expanded other pages

This commit is contained in:
John Lancaster
2026-08-30 09:19:54 -05:00
parent 12f916455b
commit f6752313be
3 changed files with 540 additions and 262 deletions
+40 -154
View File
@@ -1,176 +1,62 @@
--- ---
name: nicegui name: nicegui
description: 'Reference hub for NiceGUI and FastAPI application structure, typed configuration, ASGI and Uvicorn startup, UI composition, styling, bindable state, interactions, troubleshooting, testing, and source documentation. Use when planning, implementing, reviewing, deploying, or debugging NiceGUI applications; load only the references relevant to the task.' description: 'Build, review, and debug NiceGUI applications. Use for FastAPI or Uvicorn integration, app factories and lifespan, ui.* components, Quasar props/events/slots, Tailwind page layout, colors and dark mode, bindings and bindable_dataclass, editable ui.table cells, uploads/forms/live updates, or version-specific NiceGUI source research.'
--- ---
# NiceGUI Reference # NiceGUI Application Guide
Use this skill as a progressive reference for NiceGUI applications built with FastAPI. Start with the routing map, load only the material needed for the current question, and reconcile it with the target project's NiceGUI version and established conventions. Use this skill to choose the smallest supporting reference for a NiceGUI task. The pages cover different ownership boundaries; do not load the whole reference set.
## When to Use ## Workflow
- Planning or reviewing NiceGUI application structure and FastAPI composition. 1. Inspect the target project's pinned NiceGUI version, entry point, and existing page/component patterns.
- Building or refactoring pages, components, layouts, and static assets. 2. Match the request to one row in the routing table and load that primary reference.
- Creating editable tables with Python-authoritative state, validation, and persistence. 3. Load the optional companion only when the task crosses the boundary named in the last column.
- Modeling UI state with bindings or bindable dataclasses. 4. Prefer NiceGUI's typed constructor, binding, or helper API; descend to Quasar props, events, slots, or methods only when the wrapper does not expose the required behavior.
- Implementing forms, uploads, refreshes, live updates, or background work. 5. Validate the changed behavior with a focused test. For visual work, also check the supported mobile, landscape desktop, and portrait desktop viewports.
- Diagnosing UI state, concurrency, navigation, or asset problems.
- Verifying framework behavior against primary documentation.
## How to Use This Skill ## Task Routing
1. Classify the request using the discovery map below. | Task or symptom | Load first | Add only when |
2. Load the smallest relevant reference, or at most two references for a mixed concern. | --- | --- | --- |
3. Inspect the target repository before applying guidance; preserve its sound local patterns. | Choose package boundaries, dependency direction, page registration, health routes, or optional persistence, LangGraph, and mounted-docs placement | [application architecture](./references/architecture.md) | Add [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) for concrete ASGI ownership or startup code. |
4. Check the pinned NiceGUI and integration versions before relying on version-specific APIs. | Decide between `ui.run()` and `ui.run_with()`, compose a parent FastAPI app, define lifespan ordering, build an app factory, configure typed settings, expose a project script, or handle reload/workers | [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) | Add [application architecture](./references/architecture.md) only for wider package placement. |
5. Validate the changed behavior with focused tests and, for UI work, relevant viewport checks. | Choose a `ui.*` constructor, binding, Quasar prop, event, slot, or frontend method; diagnose model events, event payloads, scoped-slot props, detached popups, `ui.select`, or `ui.icon` | [component mechanics](./references/component-mechanics.md) | Add [source documentation](./references/source-documentation.md) when the installed wrapper or bundled Quasar version must be verified. |
| Build page shells, rows, columns, grids, widths, overflow, responsive reflow, typography, font loading, static assets, or deliberate scaling | [page structure, typography, and scaling](./references/styling-and-customization.md) | Add [component mechanics](./references/component-mechanics.md) when layout depends on a Quasar prop, slot, popup, or generated component structure. |
| Configure `app.colors()`, `ui.colors()`, semantic or fixed Quasar colors, custom color names, component color values, CSS color variables, or `ui.dark_mode()` | [NiceGUI and Quasar color theming](./references/colors-and-quasar-theming.md) | Add [page structure, typography, and scaling](./references/styling-and-customization.md) only when the task also changes physical layout or CSS loading. |
| Model typed page state with `binding.bindable_dataclass`, understand propagation and transform direction, bind nested values, avoid active-link polling, or design projection/persistence rollback | [binding dataclasses](./references/binding-dataclasses.md) | Add [component mechanics](./references/component-mechanics.md) for browser-originated event proposals. |
| Make `ui.table` cells editable with stable row keys, dataframe projections, row-scoped dataclasses, validation, touched rows, selection-preserving refresh, or `QPopupEdit` | [editable tables](./references/tables.md) | Follow its links to binding or component mechanics only when changing the underlying projection or event bridge. |
| Implement uploads, form submission, SSE versus WebSockets, background jobs, duplicate-submit guards, or explicit `@ui.refreshable` refreshes | [interaction patterns](./references/interaction-patterns.md) | Add [binding dataclasses](./references/binding-dataclasses.md) when state propagation itself is the problem. |
| Investigate upload errors, async UI races, stale assets, navigation/state drift, or perform a compact production-readiness review | [troubleshooting and quality gates](./references/troubleshooting-and-quality-gates.md) | Follow the symptom to one detailed reference above. |
| Verify a framework claim against primary NiceGUI, FastAPI, Uvicorn, Tailwind, Quasar, SQLAlchemy, Pydantic, or LangGraph documentation | [source documentation](./references/source-documentation.md) | Use a task page first when implementation guidance, not source lookup, is needed. |
## Progressive Discovery Map ## Boundary Rules
### Application Architecture - Use [application architecture](./references/architecture.md) for module ownership, not for page geometry or low-level component behavior.
- Use [page structure, typography, and scaling](./references/styling-and-customization.md) for physical layout. Use [component mechanics](./references/component-mechanics.md) for the behavior crossing NiceGUI, Quasar, Vue, and browser boundaries.
- Use [binding dataclasses](./references/binding-dataclasses.md) for the binding graph and Python model projections. Use [interaction patterns](./references/interaction-patterns.md) for user workflows such as upload, submit, refresh, streaming, and background work.
- Start editable-table work in [editable tables](./references/tables.md). It already identifies the exact binding and event sections needed by that pattern.
- Treat [source documentation](./references/source-documentation.md) as a source index, not as an implementation workflow.
Load [application architecture](./references/architecture.md) for: ## Runnable Examples
- FastAPI app factories and lifespan ownership Load an example only when its exact mechanic matches the task:
- package boundaries and dependency direction
- page registration and health routes
- optional persistence, LangGraph, or mounted documentation
- async responsiveness and baseline tests
### FastAPI And Uvicorn Startup - [binding transforms](./examples/data_binding.py): `bindable_dataclass`, `ui.date`, and typed `forward`/`backward` conversion.
- [select events](./examples/select_events.py): `on_change`, generic Quasar events, `update:model-value`, browser-to-Python payload forwarding, and programmatic value changes.
- [editable table](./examples/editable_table.py): dataframe-to-row state, named QTable cell slots, controlled editors, Python validation, touched rows, and canonical row refresh.
Load [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) for: ## Defaults That Span References
- choosing between `ui.run()` and `ui.run_with()`
- understanding the parent FastAPI app and NiceGUI's internal app
- composing ASGI lifespan and mounted routes
- loading one typed settings snapshot for server and application configuration
- serving an app instance or factory with Uvicorn
- exposing programmatic startup through `[project.scripts]`
- reload, worker, and process-local state constraints
### Styling And Customization
Load [styling and customization](./references/styling-and-customization.md) for:
- the precedence of NiceGUI Python mechanics, then Tailwind classes, then scoped CSS only as a fallback
- page shells, flex and grid tracks, stable dimensions, and overflow ownership
- Python composition APIs, Tailwind structural utilities, and Quasar-owned helpers
- font loading, family selection, type scales, line height, and line length
- responsive page composition, breakpoint boundaries, and static asset loading
- rem-based sizing, browser enlargement, CSS zoom, and transform scaling
- visual validation at supported viewport sizes
### NiceGUI And Quasar Colors
Load [NiceGUI and Quasar color theming](./references/colors-and-quasar-theming.md) for:
- the boundary between NiceGUI's Python APIs and Quasar's browser color system
- app-wide and page-level palette scope and precedence
- semantic brand roles, fixed Quasar palette colors, and custom color names
- component color value classification across Quasar, Tailwind, and CSS
- the separation between palette values and dark-mode state
### Component Mechanics
Load [component mechanics](./references/component-mechanics.md) for:
- the NiceGUI Python wrapper, element bridge, Quasar component, and Vue runtime boundaries
- deciding between constructors, bindings, Quasar props, events, slots, and frontend methods
- controlled values, model events, transformed payloads, and server-authoritative edit proposals
- server-client state and event flow, validation timing, and commit policy
- detached content and external icon assets
- source research against the installed NiceGUI and bundled Quasar versions
- `ui.select` and `ui.icon` mechanics and caveats
- scoped component slots and their interaction contracts
### Editable Tables
Load [editable tables](./references/tables.md) for:
- Python-authoritative editable `ui.table` state
- rendering dataframe records into row-scoped bindable dataclasses
- stable row identity across sorting, filtering, and pagination
- NiceGUI editors in Quasar `body-cell-*` scoped slots
- QTable row refresh and selection preservation after edits
- the full `body` slot required when escalating to `QPopupEdit`
### Bindable State
Load [bindable dataclasses](./references/binding-dataclasses.md) for:
- typed local UI state
- propagation, serializable projections, persistence, and rollback behavior
- nested structures and strict bindings
- mutable defaults, performance, and version notes
### Interaction Patterns
Load [interaction patterns](./references/interaction-patterns.md) for:
- uploads and form submission
- explicit refreshes
- server-sent events and WebSockets
- background work and duplicate-submission guards
### Troubleshooting And Quality
Load [troubleshooting and quality gates](./references/troubleshooting-and-quality-gates.md) for:
- upload failures and UI race conditions
- stale assets and navigation drift
- responsiveness, accessibility, reliability, and maintainability checks
### Primary Sources
Load [source documentation](./references/source-documentation.md) when:
- behavior is version-sensitive or uncertain
- an integration recommendation needs verification
- upstream NiceGUI, FastAPI, Tailwind, Quasar, SQLAlchemy, Pydantic, or LangGraph documentation is required
## Common Discovery Paths
### New Application Or Architecture Review
1. Load [application architecture](./references/architecture.md).
2. Add [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) when FastAPI owns the application or startup must be exposed as a project command.
3. Add [styling and customization](./references/styling-and-customization.md) only when page structure, typography, or scaling is in scope.
### Page Or Component Work
1. Load [application architecture](./references/architecture.md) for page and component ownership decisions.
2. Load [component mechanics](./references/component-mechanics.md) when behavior must be mapped across NiceGUI, Quasar, and Vue, or when detached content and component-specific behavior are involved.
3. Load [styling and customization](./references/styling-and-customization.md) for page structure, typography, responsive presentation, utility classes, or scaling; preserve its Python-then-Tailwind precedence before adding CSS.
4. Add [NiceGUI and Quasar color theming](./references/colors-and-quasar-theming.md) when palette scope, color names, theme propagation, or dark-mode boundaries matter.
5. Load [editable tables](./references/tables.md) when table cells accept user changes or `QPopupEdit` is being considered.
6. Add [interaction patterns](./references/interaction-patterns.md) or [bindable dataclasses](./references/binding-dataclasses.md) according to the page behavior.
### Debugging Or Production Review
1. Start with [troubleshooting and quality gates](./references/troubleshooting-and-quality-gates.md).
2. Follow the symptom to one detailed reference.
3. Confirm uncertain behavior in [source documentation](./references/source-documentation.md).
## General Defaults
- Keep composition, transport, services, pages, and components directionally separated. - Keep composition, transport, services, pages, and components directionally separated.
- Keep business logic out of UI components and event handlers. - Keep business logic out of UI components and event handlers.
- Avoid blocking I/O and CPU-heavy work in the UI event loop. - Avoid blocking I/O and CPU-heavy work in the UI event loop.
- Prefer event-driven updates and explicit refreshes over unrelated polling. - Prefer event-driven updates and explicit refreshes to unrelated polling.
- Discover component capabilities through NiceGUI docs and constructors, then the wrapped Quasar API. - Keep browser-originated values as proposals; validate and normalize them in Python before mutating authoritative state.
- Keep editable table records authoritative in Python; send stable row keys with edit proposals and reassert canonical rows after validation. - Use Tailwind for physical structure and scoped static CSS only for requirements that NiceGUI, Quasar, or utilities cannot express cleanly.
- Research the current NiceGUI and Quasar source documentation before generating component-specific code or CSS.
- Prefer constructor arguments and native Quasar features through NiceGUI; use Tailwind for structure and scoped static CSS for stable fine tuning.
- Provide loading, success, and failure states for user-triggered work. - Provide loading, success, and failure states for user-triggered work.
- Treat version-specific guidance as a prompt to verify the project's dependency version. - Verify component-specific behavior against the installed NiceGUI version and its bundled Quasar version.
## Reference Use Contract ## Completion Check
When applying this skill: Before finishing, distinguish target-repository facts from reference recommendations, cite the supporting page used for framework-specific claims, state unresolved assumptions, and report the focused behavior and viewport checks performed.
- return only guidance relevant to the current task
- distinguish repository facts from reference recommendations
- cite the appropriate source reference for framework-level claims
- state assumptions when application requirements are missing
- report the focused checks used to validate implementation changes
@@ -1,110 +1,265 @@
# Interaction Patterns Reference # NiceGUI Interaction Mechanics
## Reactive State Use this reference for user-driven and live application behavior: page and client lifetime, value validation, form submission, uploads, explicit refreshes, timers, application events, background execution, and server-pushed updates. Component-specific event names, scoped event payloads, and Quasar model contracts are covered in [component mechanics](./component-mechanics.md). Binding graph behavior and typed projections are covered in [binding dataclasses](./binding-dataclasses.md).
Use bindable dataclasses for local page state. The NiceGUI implementation details below are verified against NiceGUI `3.16.0`. FastAPI's native `EventSourceResponse` and `ServerSentEvent` APIs require FastAPI `0.135.0` or later. Check the target application's pinned versions before depending on those surfaces.
## Interaction Boundary Map
| Boundary | Owns | Does not own |
| --- | --- | --- |
| NiceGUI element | browser-facing value, enabled state, validation display, and registered UI callbacks | domain authorization, durable persistence, or cross-worker coordination |
| Page `Client` | one page visit's elements, UI context, socket connection, outbox, and client-scoped storage | durable user identity or shared application state |
| Page state | current filters, drafts, selections, busy flags, and serializable projections | database transactions or durable job state |
| Service or repository | domain validation, authorization, transactions, idempotency, and persistence | direct creation or mutation of NiceGUI elements |
| NiceGUI task utility | scheduling work in the event loop, a thread, or a process | durable delivery after process failure |
| FastAPI route | HTTP, SSE, or custom WebSocket protocol and authentication boundary | automatic synchronization with NiceGUI elements |
NiceGUI already uses a Socket.IO connection to carry element events and server updates for each client. Ordinary page interactions should use component callbacks, bindings, `Event`, and element updates rather than introducing a second transport.
## Page And Client Lifetime
A [`@ui.page`](https://nicegui.io/documentation/page) builder creates a private `Client` and element tree for each page visit. During initial page construction, Python can create elements before the browser socket exists. Code that requires JavaScript, tab storage, or post-response work must first await `ui.context.client.connected()`.
The tagged [`page` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/page.py) distinguishes two phases:
1. Before connection, the page builder must produce the initial response within `response_timeout`, which defaults to three seconds.
2. Once `connected()` is awaited, NiceGUI can send the initial HTML immediately and let the remaining async builder continue with a live client.
Long service calls should not delay initial page construction. Render a stable loading state, await the connection where necessary, then perform the asynchronous work and update or refresh the bounded result region.
### Disconnect, Reconnect, And Delete
The tagged [`Client` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/client.py) treats a transient socket disconnect differently from client deletion:
- `on_disconnect` runs whenever the socket disconnects, including interruptions followed by reconnection.
- NiceGUI keeps the client alive for the page's `reconnect_timeout`.
- A successful handshake within that window cancels pending deletion.
- `on_delete` runs only when the client is actually removed after the reconnect window or explicit cleanup.
- Deletion removes the client's elements and bindings and stops its outbox.
Use `on_disconnect` for connection telemetry and reversible transport state. Use `on_delete` to release resources owned by the page visit. Do not close a page-owned resource on every disconnect if it must survive a short reconnect.
NiceGUI's tagged [`Outbox`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/outbox.py) retains recent messages according to `message_history_length` and the reconnect window. A reconnecting client supplies its next expected message ID; NiceGUI replays retained messages or reloads the page when the required history is unavailable. Message replay is transport recovery, not a durable event log or a substitute for idempotent service operations.
### State Scope
[`app.storage`](https://nicegui.io/documentation/storage) offers scopes with different navigation and process lifetimes:
| Scope | Shared with | Survives page navigation or reload | Persistence notes |
| --- | --- | --- | --- |
| `client` | current page visit only | no | server memory; appropriate for short-lived page resources |
| `tab` | current browser tab | yes | server memory by default; requires an established connection |
| `user` | tabs carrying the same signed session ID | yes | server-side persistent dictionary; requires `storage_secret` |
| `browser` | tabs sharing the session cookie | yes | cookie payload; writable only before the response is built; prefer `user` for most data |
| `general` | all users in the process or configured backend | yes | shared persistent dictionary; not a per-user boundary |
The tagged [`storage` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/storage.py) stores general and user data in local JSON files by default or Redis when configured. Tab storage is process memory unless Redis is configured. Multiple workers therefore require an explicitly shared backend for state that must cross processes.
## Values, Validation, And Submission
NiceGUI value elements mirror browser changes into Python and then invoke `on_change` or `on_value_change` handlers. For text input, [`ui.input`](https://nicegui.io/documentation/input) sends `on_change` on each value change unless a Quasar `debounce` prop delays the model update. Use an enter, blur, or explicit submit event when every keystroke should not trigger application work.
The tagged [`ValidationElement`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/mixins/validation_element.py) implements NiceGUI's Python validation:
- a callable returns an error string or `None`
- a dictionary maps error strings to predicates and stops at the first failed predicate
- automatic validation runs after each handled value change unless `without_auto_validation()` is set
- `validate()` updates the element's `error` and `error-message` props
- asynchronous validation runs as a background task; `validate(return_result=True)` is not supported for an async validator
NiceGUI validation is suitable for field feedback, but a submit operation still needs service-level validation and authorization. Browser values, client-side Quasar rules, file metadata, and hidden or disabled controls are not trust boundaries.
NiceGUI does not require a transport-level HTML form for ordinary page submission: current element values already exist in Python. A submit handler can validate relevant fields, construct an immutable command or DTO, call the service boundary, and update the page from the accepted result. Clear draft state only after persistence succeeds.
```python ```python
from dataclasses import field async def submit() -> None:
from nicegui import binding, ui if not all(field.validate() for field in (name, email)):
return
@binding.bindable_dataclass submit_button.disable()
class PageState:
selected_id: int | None = None
items: list = field(default_factory=list)
state = PageState()
ui.label().bind_text_from(state, "selected_id")
```
## File Upload Pattern
- Validate extension and size before storing.
- Delegate storage to a service method.
- Notify success and failure explicitly.
```python
async def handle_upload(e: ui.events.UploadEventArguments):
try: try:
if e.size > 10 * 1024 * 1024: user = await user_service.create(name=name.value, email=email.value)
raise ValueError("File too large") ui.notify(f"Created {user.display_name}", type="positive")
if not e.name.endswith(".pdf"): name.set_value("")
raise ValueError("Only PDF allowed") email.set_value("")
await file_service.store(e.content.read(), e.name) except DuplicateEmailError:
ui.notify(f"Uploaded: {e.name}", type="positive") email.error = "This email is already registered"
except ValueError as err: finally:
ui.notify(str(err), type="negative") submit_button.enable()
ui.upload(on_upload=handle_upload, auto_upload=True)
``` ```
## Form Submission Pattern For asynchronous field validators, await the validator at the service boundary or maintain an explicit validation state; do not use the synchronous return value of `validate()` as proof that asynchronous validation completed.
- Bind UI inputs to dataclass fields. ## Upload Mechanics
- Perform validation in the service layer.
- Clear form state on success. [`ui.upload`](https://nicegui.io/documentation/upload) wraps Quasar's `QUploader`. The tagged [`Upload` wrapper](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload.py) registers a POST route scoped to the current client and element. Its event order is:
1. `on_rejected` during browser-side file selection for Quasar restrictions.
2. `on_begin_upload` when the client starts a request.
3. `on_upload` once for each server-received file.
4. `on_multi_upload` after all files in that request have been converted.
`max_file_size`, `max_total_size`, `max_files`, and an `accept` prop improve client feedback, but NiceGUI's [security guidance](https://nicegui.io/documentation/section_security#examples_are_starting_points) identifies those restrictions as browser-side checks. Revalidate size, media type, content signature, filename policy, authorization, and storage quota on the server before persisting or parsing data.
In NiceGUI 3.16, `event.file` is a [`FileUpload`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload_files.py):
| Surface | Behavior |
| --- | --- |
| `name` | basename sanitized by NiceGUI; still untrusted display metadata |
| `content_type` | request-provided media type; not content verification |
| `size()` | synchronous byte count |
| `read()`, `text()`, `json()` | asynchronous full-content reads |
| `iterate(chunk_size=...)` | asynchronous chunks for bounded-memory processing |
| `save(path)` | asynchronous save to an application-selected path |
NiceGUI reads the incoming Starlette upload and keeps it in memory up to `MultiPartParser.spool_max_size`; larger files spill to a temporary file. This spool threshold controls memory versus disk, not the allowed upload size. Raising it increases per-upload memory pressure and should not be used as a validation mechanism.
```python ```python
@binding.bindable_dataclass from nicegui import events, ui
class FormData:
name: str = ""
email: str = ""
data = FormData()
ui.input("Name").bind_value(data, "name")
ui.input("Email").bind_value(data, "email")
async def on_submit(): async def handle_upload(event: events.UploadEventArguments) -> None:
file = event.file
if file.size() > 10 * 1024 * 1024:
ui.notify("File exceeds 10 MB", type="negative")
return
if file.content_type != "application/pdf":
ui.notify("Only PDF files are accepted", type="negative")
return
try: try:
await user_service.create_user(name=data.name, email=data.email) await file_service.store(chunks=file.iterate(), original_name=file.name)
ui.notify("User created", type="positive") except StorageQuotaError:
data.name = data.email = "" ui.notify("Storage quota exceeded", type="negative")
except ValueError as err: else:
ui.notify(str(err), type="negative") ui.notify(f"Uploaded {file.name}", type="positive")
ui.button("Submit").on_click(on_submit)
uploader = ui.upload(
on_upload=handle_upload,
on_rejected=lambda: ui.notify("File rejected", type="negative"),
max_file_size=10 * 1024 * 1024,
auto_upload=True,
).props("accept=application/pdf")
``` ```
## Real-Time Updates Decision Generate the durable storage name independently from `file.name`, keep user-uploaded active content off the application origin, and apply content-specific scanning before downstream parsers consume the file. Call `uploader.reset()` when the product should clear QUploader's client-side queue after a completed or abandoned operation.
Use SSE for one-way status streaming. ## Element Updates And Refreshable Regions
Use WebSocket for bidirectional messaging.
SSE endpoint example: Use the narrowest update mechanism that represents the change:
```python | Change | Appropriate surface |
@app.get("/events/status") | --- | --- |
async def status_stream(): | one wrapper property | setter, binding, or property assignment supported by that wrapper |
async def gen(): | mutated option or row collection | wrapper helper or explicit `element.update()` |
while True: | a bounded subtree whose structure changed | `@ui.refreshable` or `@ui.refreshable_method` |
yield f"data: {await get_status()}\\n\\n" | navigation to a different page | `ui.navigate` or `ui.sub_pages` |
await asyncio.sleep(1)
return StreamingResponse(gen(), media_type="text/event-stream")
```
## Background Work Pattern The tagged [`refreshable` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/functions/refreshable.py) records every invocation as a target container. `refresh()` clears and recreates each matching target; it does not diff children. Arguments passed to `refresh()` replace prior positional arguments when non-empty and update prior keyword arguments.
- Start long jobs in FastAPI background tasks. A module-level refreshable called by multiple clients has multiple targets, so refreshing it can update all surviving targets. Define the decorated function inside the page, create a page-local decorated wrapper, or use a per-page object with `@ui.refreshable_method` when clients need independent refresh behavior.
- Expose status via endpoint or streaming channel.
- Guard buttons against duplicate submissions during in-flight tasks.
## Explicit Refresh Pattern For asynchronous refreshable functions:
Use @ui.refreshable and call refresh intentionally instead of polling unrelated state. - `await region.refresh()` waits for all matching async refreshes to finish
- calling `region.refresh()` without awaiting schedules the async work in the background
- awaiting is appropriate when a button must remain disabled until rendering completes
- each refresh clears the old target before the new async render finishes, so provide a stable outer loading surface when an empty interval would be disruptive
```python `ui.state()` is local storage indexed by call order inside one refreshable target. It can only be called inside a refreshable function, and conditional changes to state-call order can associate values with the wrong logical state. Use typed page state or bindable dataclasses when state identity must remain explicit.
@ui.refreshable
async def item_list():
items = await service.list()
for item in items:
ui.label(item.name)
ui.button("Refresh").on_click(lambda: item_list.refresh()) ## Timers And Application Events
```
## Links [`ui.timer`](https://nicegui.io/documentation/timer) is client-scoped. Its tagged [element implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/timer.py) waits for the client connection and cancels the current invocation when the element is deleted. `app.timer` is application-scoped and has no UI context of its own.
!!! info "Primary sources" The tagged base [`Timer`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/timer.py) awaits each callback before scheduling the remainder of the interval, so one timer does not overlap its own invocations. A callback that takes longer than the interval causes the next iteration to begin without an additional delay. `deactivate()` pauses future invocations, while `cancel(with_current_invocation=True)` also cancels the current callback task and cannot be reversed.
- [NiceGUI action events](https://nicegui.io/documentation/section_action_events)
- [FastAPI server-sent events](https://fastapi.tiangolo.com/advanced/server-sent-events/) Use timers for truly periodic observation, not to compensate for a missing event or explicit refresh. Polling intervals must account for query cost, number of connected clients, and process-local duplication under multiple workers.
- [FastAPI WebSockets](https://fastapi.tiangolo.com/advanced/websockets/)
[`Event`](https://nicegui.io/documentation/event) decouples long-lived Python producers from UI subscribers:
- `emit()` invokes subscribers without waiting for async callbacks to complete
- `call()` awaits all subscribers and propagates their failures to the caller
- `emitted(timeout=...)` waits for the next emission
- subscriptions created in a UI context are automatically removed when that client is deleted unless configured otherwise
The automatic unsubscribe behavior in the tagged [`Event` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/event.py) makes an application event suitable for connecting longer-lived models to page-local UI without retaining deleted clients. It remains process-local; use a broker or shared service for cross-worker fan-out.
## Execution Contexts
Choose an execution surface by workload and lifetime:
| Surface | Execution | Suitable for | Important constraint |
| --- | --- | --- | --- |
| async UI handler | event loop | non-blocking clients and short orchestration | blocking calls freeze all clients on that loop |
| `run.io_bound()` | shared thread pool | blocking file, HTTP, or SDK calls | cancellation does not necessarily stop the underlying thread operation |
| `run.cpu_bound()` | process pool | CPU-heavy pure computation | callable, arguments, result, and failures cross a pickle boundary |
| `background_tasks.create()` | event-loop task | detached async work owned by this process | canceled during shutdown unless tagged with `await_on_shutdown` |
| FastAPI `BackgroundTasks` | after an HTTP response | small route-triggered work | still belongs to the web process; not a durable queue |
| external worker or job queue | separate process or service | durable, retryable, resource-heavy jobs | requires explicit status, cancellation, and result contracts |
The tagged [`run` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/run.py) uses a thread pool for `io_bound` and a process pool for `cpu_bound`. For CPU work, prefer a module-level function with simple serializable arguments and return data rather than UI objects or closures. NiceGUI 3.16 inherits the platform multiprocessing start method unless `run.process_pool_start_method` is set before startup; `spawn` avoids unsafe fork behavior in a threaded process but does not inherit module state.
The tagged [`background_tasks` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/background_tasks.py) keeps strong references to running tasks, forwards unhandled exceptions to global exception handlers, and cancels ordinary tasks during shutdown. `create_lazy()` coalesces repeated work by name into the current run plus only the latest waiting coroutine; it is useful for refresh-style invalidation, not for work where every event must be processed.
## Live Update Transports
| Requirement | Default surface |
| --- | --- |
| update the initiating NiceGUI page | mutate elements or bound page state in its client context |
| notify all local clients of a page | iterate `app.clients(path)` and enter each `with client:` context |
| connect a long-lived Python producer to page subscribers | NiceGUI `Event` with page-local subscriptions |
| one-way HTTP event stream for an external/browser consumer | FastAPI SSE endpoint |
| custom bidirectional protocol independent of NiceGUI elements | FastAPI WebSocket endpoint |
| cross-worker or cross-instance broadcast | external broker plus a subscriber in each process |
FastAPI's [SSE support](https://fastapi.tiangolo.com/tutorial/server-sent-events/) uses a yielding route with `response_class=EventSourceResponse`. `ServerSentEvent` adds `event`, `id`, `retry`, and comment fields; event IDs support application-defined resume behavior through `Last-Event-ID`. FastAPI supplies keep-alive comments and headers that discourage proxy buffering and caching. The stream producer still owns authorization, disconnect-aware resource cleanup, replay semantics, and bounded buffering.
FastAPI [WebSockets](https://fastapi.tiangolo.com/advanced/websockets/) support text, bytes, and JSON in both directions. Catch `WebSocketDisconnect`, remove the connection from any local registry, and remember that an in-memory connection manager reaches only clients attached to the same process.
Do not use SSE or a custom WebSocket merely to update NiceGUI elements. Those transports do not automatically establish the target NiceGUI client context or synchronize its element tree.
## Concurrency And Feedback State
Disabling the initiating control communicates that work is active, but it is not a server-side concurrency guarantee. Also guard the handler or service with one of these policies:
- reject a second request while the operation is in flight
- coalesce duplicate refresh requests and keep only the latest invalidation
- serialize operations with a lock scoped to the affected entity or user
- make the service operation idempotent and return the existing result
For search, filtering, and other replaceable reads, an older request can complete after a newer request. Associate each request with a monotonically increasing generation or cancel the previous task, and only publish a result that still matches the current generation. Cancellation must still restore enabled/loading state in `finally`.
Every user-triggered asynchronous operation should expose a bounded state model such as `idle`, `running`, `succeeded`, `failed`, or `canceled`. Keep the error message near the action, preserve user input after expected failure, and do not convert unexpected programming errors into a generic success-like state.
## Source Index
!!! info "NiceGUI public documentation"
- [Pages and client connection](https://nicegui.io/documentation/page)
- [Action, events, execution, and error handling](https://nicegui.io/documentation/section_action_events)
- [Input and validation](https://nicegui.io/documentation/input)
- [Upload](https://nicegui.io/documentation/upload)
- [Refreshable UI](https://nicegui.io/documentation/refreshable)
- [Timer](https://nicegui.io/documentation/timer)
- [Application events](https://nicegui.io/documentation/event)
- [Storage scopes](https://nicegui.io/documentation/storage)
!!! info "NiceGUI `3.16.0` implementation"
- [Page builder and response phases](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/page.py)
- [Client lifecycle](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/client.py)
- [Outbox and reconnect replay](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/outbox.py)
- [Validation elements](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/mixins/validation_element.py)
- [Upload wrapper](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload.py)
- [Uploaded-file storage and access](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload_files.py)
- [Refreshable targets and local state](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/functions/refreshable.py)
- [Timer scheduling](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/timer.py)
- [Application event dispatch](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/event.py)
- [Thread and process execution](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/run.py)
- [Background-task lifecycle](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/background_tasks.py)
!!! info "FastAPI transports and tasks"
- [Server-sent events](https://fastapi.tiangolo.com/tutorial/server-sent-events/)
- [WebSockets](https://fastapi.tiangolo.com/advanced/websockets/)
- [Response background tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/)
@@ -1,39 +1,276 @@
# Troubleshooting and Quality Gates # NiceGUI Troubleshooting And Quality Evidence
## Troubleshooting Use this reference to identify the layer that owns a NiceGUI failure and the evidence needed to distinguish similar symptoms. It covers behavior verified against NiceGUI `3.16.0`; browser, Quasar, Vue, FastAPI, Socket.IO, Uvicorn, and proxy behavior must also be checked against the versions deployed by the target application.
### Upload Errors The companion [interaction mechanics](./interaction-patterns.md) page defines normal lifecycle, validation, upload, refresh, timer, task, and transport behavior. [Component mechanics](./component-mechanics.md) covers Quasar props, events, slots, wrapper models, and frontend payload mapping. [FastAPI and Uvicorn startup](./fastapi-uvicorn-startup.md) covers import identity, workers, reload, and deployment topology.
- Validate extension and size before storage. ## Diagnostic Index
- Catch expected exceptions and return negative notifications.
- Log unexpected exceptions with request context.
### UI Race Conditions | Symptom | Likely owner | Discriminating evidence |
| --- | --- | --- |
| upload rejected before handler runs | Quasar `QUploader` or browser selection rules | `on_rejected` fires; no upload POST reaches the server |
| upload request returns `400` or `413` | proxy, ASGI multipart parsing, NiceGUI upload route, or application validation | HTTP status and response body from the upload request; proxy and server logs |
| page shows a response-timeout error | async page construction before client connection | warning naming `response_timeout`; page builder timing and `connected()` boundary |
| update appears only after reload | wrong client context, unobserved plain mutation, or missing explicit update | target `Client`, element deletion state, outbox traffic, and wrapper update call |
| update reaches one tab but not another | private page element tree or process-local fan-out | client IDs, page paths, worker identity, and `app.clients(path)` iteration |
| updates disappear after a brief network interruption | client deleted after reconnect timeout or outbox replay unavailable | disconnect/delete timestamps, reconnect timeout, next message ID, and reload log |
| old query result replaces a newer one | concurrent async completion race | request generation, start/end timestamps, query identity, and publish order |
| all clients pause during one action | blocking work on the event loop | event-loop lag and stack or profile showing synchronous I/O or CPU work |
| callback runs repeatedly after navigation | duplicate timer, event subscription, or lifecycle registration | registration count, client IDs, delete handlers, and task names |
| user state leaks across tabs or users | incorrect storage scope or module-level mutable state | storage scope, session ID, tab ID, process ID, and object identity |
| URL changes but content or state does not | History API used without a route/content transition | `pushState`/`replaceState` call versus `ui.navigate.to` or sub-page routing |
| changed CSS or image remains stale | static cache lifetime or proxy/browser cache | response URL, `Cache-Control`, cache source in developer tools, and content version |
| exception is logged but no page feedback appears | exception occurred outside an active UI slot or after the client was deleted | exception handler invoked, current client/slot, task owner, and element state |
- Disable triggering controls during async work. Start with the smallest boundary that can explain the symptom. Browser developer tools establish whether an event, upload, static request, or socket message crossed the network. Server logs establish whether the page, client, handler, task, or service received it. Durable data inspection establishes whether the accepted operation committed independently of the UI.
- Remove duplicate timers and listeners targeting the same state.
- Ensure service call ordering is deterministic before render updates.
### Asset Caching ## Upload Failures
- Confirm static mount and proxy rewrite correctness. [`ui.upload`](https://nicegui.io/documentation/upload) is a Quasar uploader backed by an element-specific NiceGUI POST route. The tagged [`Upload` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload.py) resolves the `client_id` and element ID from the route before converting each Starlette upload to `event.file`.
- Add cache-busting query strings for changed assets.
- Avoid per-page CSS injection.
### Navigation and State Drift ### Rejected Before Transfer
- Avoid global mutable UI state. `max_file_size`, `max_total_size`, `max_files`, and the Quasar `accept` prop operate in the browser. A rejection at this stage calls `on_rejected`; it does not prove that the server would reject an equivalent direct request. An unexpected rejection commonly comes from MIME patterns, file-count state retained in the uploader queue, or size units that differ from the intended policy.
- Keep state request-scoped or service-managed.
- Rehydrate page data during route load.
## Production Readiness Gate Useful evidence includes the selected file's browser-reported type and size, current queue contents, configured Quasar props, and whether `on_begin_upload` or a network request occurs. Reset the uploader queue only when clearing previous selections is the intended product behavior.
Pass all checks before shipping: ### Transfer Or Multipart Failure
- Structure: one-way dependencies between pages, components, and services. If the POST begins but the upload handler does not run, inspect the HTTP status before changing page code:
- Responsiveness: UI validated at both small and large viewport widths.
- Accessibility: labels and actions are clear and readable.
- Reliability: validation and exception paths surface user feedback.
- Maintainability: repeated UI patterns are extracted; business logic remains in services.
If any check fails, return to the workflow step that owns that concern. | Response | Common boundary |
| --- | --- |
| `404` | stale or deleted element/client route, incorrect proxy prefix, or navigation during transfer |
| `400` | malformed multipart body, missing `client_id`, missing element ID, or no matching uploader element |
| `413` | reverse-proxy or ASGI request-size limit |
| `422` | route or dependency validation outside the normal NiceGUI upload route |
| `5xx` | multipart conversion, temporary storage, application handler, or downstream service failure |
The tagged [`FileUpload` conversion](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload_files.py) keeps small uploads in memory and spills larger ones to a temporary file after Starlette's `MultiPartParser.spool_max_size`. This is a buffering threshold, not an acceptance limit. Concurrency multiplies memory and temporary-disk pressure, so record file size, concurrent upload count, process memory, temporary filesystem capacity, and proxy limits together.
### Accepted But Unsafe Or Corrupt
`file.name` is reduced to its basename by NiceGUI, and `file.content_type` comes from the request. Neither establishes safe content. Server-side acceptance should record the authoritative byte size and verify content signature, parser behavior, quota, authorization, and application-selected destination. Use an independently generated storage key and keep active user content off the main application origin.
When downstream parsing fails, distinguish transport completion from domain acceptance. A successful upload POST can still produce a rejected document. Preserve an operation ID or storage record so logs and user feedback identify the same attempt without logging file content or sensitive form fields.
## Initial Page Response Failures
The tagged [`page` wrapper](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/page.py) gives async page construction `response_timeout` seconds, three by default, to finish or signal that it is waiting for the client connection. If neither happens, NiceGUI cancels the page task, deletes that client, logs a warning, and serves a terminal `500` page through a fresh client.
Increasing `response_timeout` can be appropriate for bounded, unavoidable initial construction, but it does not make long I/O responsive. The relevant timing split is:
- code before `await ui.context.client.connected()` delays the initial HTTP response
- code after that await runs with a connected browser and can progressively update the page
- synchronous blocking work in either phase can still stall the event loop
Capture elapsed time around dependencies, database calls, remote clients, serialization, and component construction. A timeout with low service latency may indicate a page builder waiting on a condition that itself requires the browser connection.
Synchronous page-builder exceptions and async exceptions raised before the response is built can render an `app.on_page_exception` page. That handler is synchronous in NiceGUI 3.16. A returned FastAPI `Response` bypasses normal page rendering. Do not assume a global `app.on_exception` handler can reconstruct a failed initial element tree.
## Connection, Reconnect, And Deleted Clients
The tagged [`Client` lifecycle](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/client.py) separates a socket disconnect from deletion. On disconnect, NiceGUI invokes disconnect handlers and waits for `reconnect_timeout`; a successful handshake cancels deletion. If no connection returns, NiceGUI closes tab storage as needed, invokes delete handlers, removes elements and bindings, stops the outbox, and removes the client from `Client.instances`.
### Stale Client Writes
Holding an element, slot, timer, or client in a long-lived object can outlive the page that created it. Writes after deletion trigger NiceGUI's deleted-client warning and cannot produce a valid browser update. Before publishing detached work, retain the intended client deliberately and check `client.is_deleted` or membership in the current client set. A durable job result should be written to durable state even when its original page no longer exists; a later page load can rehydrate it.
Do not treat `on_disconnect` as final resource disposal. It also runs for reconnectable interruptions. Page-owned cleanup belongs in `on_delete`; transport telemetry and reversible status belong in `on_disconnect` and `on_connect`.
### Reconnect Replay And Reload
The tagged [`Outbox`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/outbox.py) retains recent element updates and messages. During handshake, the browser provides the next message ID it expects. NiceGUI rewinds retained history and replays from that ID. If the ID is no longer available because of age or `message_history_length`, NiceGUI reloads the page.
This mechanism explains several superficially similar outcomes:
| Outcome | Interpretation |
| --- | --- |
| short interruption, state continues | client survived and required messages remained in history |
| interruption followed by reload | rewind target was unavailable or browser initiated a reload |
| interruption followed by fresh page state | original client was deleted and route rebuilt a new element tree |
| durable operation duplicated after reconnect | application command lacked idempotency; outbox replay is not a transaction protocol |
Correlate client ID, document or tab identity, message IDs, disconnect duration, reconnect timeout, and process ID. A load-balanced multi-worker deployment also needs compatible session affinity and shared application state; an in-memory client exists only in the worker that created it.
## Missing Or Misrouted Updates
Each page client owns a private element tree. Mutating an element affects that element's client; mutating a plain list or model that has no active binding does not enqueue a browser update by itself. Check these in the owning layer:
- the element has not been deleted or replaced by a refresh
- the handler runs in the intended client's slot context
- the wrapper property is bindable or followed by its documented helper or `update()`
- the refreshable target belongs to the intended client
- application-wide producers iterate the intended `app.clients(path)` and enter each client context
- process-local events are not assumed to reach clients connected to another worker
A module-level `@ui.refreshable` can accumulate targets from several clients. Its tagged [`refresh()` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/functions/refreshable.py) clears and rebuilds every matching surviving target. Unexpected cross-client refresh therefore indicates target scope, not shared DOM. Unexpectedly missing refresh often indicates that the target container was deleted or the code refreshed a different decorated instance.
## Async Races And Duplicate Actions
NiceGUI event handlers that return awaitables are scheduled as background tasks. Disabling a button reduces normal repeated clicks but does not serialize direct requests, reconnect replays, keyboard submission, another control, or another client.
### Completion-Order Races
For replaceable reads such as search, an older request can finish after a newer request and overwrite its result. Record a generation or request key when work starts and compare it immediately before publishing. Cancellation can reduce wasted work but is not sufficient when the underlying thread, remote service, or database operation cannot be canceled.
For writes, define the service-level policy explicitly: lock, optimistic entity version, idempotency key, conflict response, or accepted duplicate semantics. UI busy state is feedback, not concurrency control.
### Refresh Races
Each refreshable invocation owns a target container. Refresh clears that target before recreating children; concurrent refreshes can therefore interleave service reads and rendering. Await a refresh when the triggering action depends on completion, serialize refreshes for one target, or use a latest-generation policy for replaceable data. Keep long-lived loading and error indicators outside the cleared target if they must remain stable.
### Observable Evidence
For an asynchronous interaction, logs are most useful when they contain operation ID, client ID, user or tenant identifier where safe, entity ID, request generation, start and finish time, outcome, and exception type. Avoid recording secrets, raw uploaded content, session cookies, or full form payloads.
## Blocking Work And Event-Loop Lag
An `async def` callback does not make synchronous work non-blocking. CPU-heavy loops, synchronous HTTP clients, filesystem calls, image or document parsers, and blocking database drivers executed on the event loop delay socket heartbeats, all clients' event handlers, timers, page responses, and outbox delivery.
Use the execution boundary defined in [interaction mechanics](./interaction-patterns.md#execution-contexts): non-blocking async APIs in the event loop, `run.io_bound()` for blocking I/O, `run.cpu_bound()` for serializable CPU work, or an external worker for durable jobs. A thread keeps the loop responsive but does not remove memory, timeout, thread-safety, or cancellation constraints. A process pool adds serialization and process-start constraints.
Evidence for event-loop blocking includes simultaneous latency across unrelated clients, delayed timers or Socket.IO heartbeats, event-loop lag metrics, and a stack or profile inside synchronous work. A single slow awaited network request that yields control does not by itself block other clients.
## Timer, Listener, And Task Duplication
Repeated callbacks usually originate at registration, not dispatch. Common ownership mistakes include:
- creating `ui.timer` repeatedly during a refresh while retaining the old timer outside the cleared container
- registering an application timer or lifecycle handler during a per-client page build
- subscribing a long-lived `Event` outside a UI context without later unsubscribing
- starting a new consumer task on every reconnect instead of once at application startup
- reloading a development process while an external scheduler still targets both old and new instances
In NiceGUI 3.16, a page-scoped `ui.timer` waits for its client connection and is canceled when its element is deleted. An `app.timer` is process-scoped. An `Event` subscription made inside a UI context is automatically removed on client deletion by default; one made outside UI context has no automatic client owner. Application lifecycle handlers and external broker consumers need an application-level owner and shutdown path.
Record timer or task name, registration site, process ID, client ID when applicable, activation state, and cancellation reason. Count registrations directly rather than inferring duplication from repeated business effects, which could also come from retries or multiple workers.
## Storage And Navigation Drift
State drift often comes from assigning data to a scope with the wrong lifetime:
| Unexpected behavior | Scope to inspect |
| --- | --- |
| state disappears on reload or route navigation | `app.storage.client` or page-local Python object |
| state unexpectedly follows another tab | `app.storage.user`, `browser`, or module-global state |
| state is missing immediately after page construction | `app.storage.tab` accessed before `client.connected()` |
| state differs between workers | local file storage, in-memory tab state, or module-global state |
| browser storage mutation raises or is ignored | `app.storage.browser` changed after response construction |
The tagged [`storage` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/storage.py) persists `user` and `general` scopes locally by default or in Redis when configured. Tab storage is in-memory unless Redis is configured. The signed browser cookie identifies a user storage record; storage scope is not authorization, and persisted identifiers must still be checked against the authenticated principal and tenant.
[`ui.navigate.to`](https://nicegui.io/documentation/navigate) opens a route, client element anchor, or external URL. With `ui.sub_pages`, a relative same-app route can be handled within the current client. `ui.navigate.history.push()` and `.replace()` only change browser history state and the visible URL; they do not invoke a page builder or rehydrate content. A URL/content mismatch after `pushState` is therefore expected unless application code also owns the content transition.
A full navigation or reload creates a new page client, so page-local objects and client storage are not durable navigation state. Encode shareable state in route or query parameters, place tab- or user-lifetime state in the matching storage scope, and reload authoritative data from services rather than retaining element instances globally.
## Static Assets, Media, And Cache Boundaries
The tagged [`Client.build_response()`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/client.py) marks NiceGUI page and Markdown responses `Cache-Control: no-store`. A proxy that caches page HTML against that header can serve stale client IDs, initial state, or user-specific content and is misconfigured.
Static files intentionally use a different policy. In NiceGUI 3.16:
- `app.add_static_files()` and `app.add_static_file()` default to `Cache-Control: public, max-age=3600`
- `max_cache_age=0` requests immediate revalidation behavior but does not create a private authorization boundary
- media routes support byte-range streaming and should be used for seekable audio or video
- static and media directory helpers explicitly expose their contents without per-file application authorization
- `single_use=True` removes a route after the first handled request in one process; it is not a secure, distributed, or retry-safe download grant
The implementation is defined by [`app.add_static_*` and `app.add_media_*`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/app/app.py) and [`CacheControlledStaticFiles`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/staticfiles.py).
For stale assets, inspect the actual response in browser developer tools: final URL after proxy rewriting, status, `Cache-Control`, `ETag` or modification metadata, service-worker involvement, and whether the response came from memory, disk, intermediary, or origin. Prefer content-versioned URLs for immutable assets. Query-string cache busting works only when every cache key includes the query and the origin serves the updated bytes.
Security-sensitive files belong behind an authenticated FastAPI route or object-store authorization mechanism with private cache policy. A hard-to-guess static URL is not access control, and public cache headers can retain content beyond logout or permission changes.
## Exception Surfaces
NiceGUI exceptions have different user-feedback capabilities according to where they occur:
| Failure surface | Handler path | UI context available |
| --- | --- | --- |
| page builder before response | `app.on_page_exception`, FastAPI handlers, then global exception handlers | fresh error-page client for synchronous page handler |
| UI event or awaited callback in an element slot | client in-page exception handlers plus global handlers | originating slot while client remains alive |
| timer or NiceGUI background task | global handler; in-page handler only when task retained an active slot context | depends on captured context and client lifetime |
| `Event.emit()` subscriber | exception forwarded to global handling | subscriber's captured slot when available |
| `Event.call()` subscriber | exception propagates to caller | caller decides feedback and transaction behavior |
| FastAPI route outside NiceGUI page UI | FastAPI exception handling | no implicit NiceGUI element context |
The tagged [`app.handle_exception()`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/app/app.py) first invokes a client's in-page exception handling when a client and slot are active, then invokes global exception handlers. Unexpected exceptions should retain a traceback and correlation ID in server logs. User feedback should be specific for expected domain failures and generic for unexpected failures, without exposing internals.
An exception notification is not recovery by itself. Restore busy state in `finally`, preserve user input after a recoverable failure, reconcile uncertain write outcomes from the authoritative store, and stop publishing to deleted clients.
## Security-Sensitive Boundaries
The following client-visible mechanisms improve usability but do not enforce policy:
- disabled or hidden controls
- Quasar input rules and upload restrictions
- route names, element IDs, client IDs, or unguessable-looking static paths
- values retained in page, tab, browser, or user storage
- custom JavaScript validation or transformed event payloads
Authorization, tenant boundaries, accepted fields, type and range checks, optimistic concurrency, upload inspection, and durable write constraints belong on the server. For every mutation, identify the authenticated principal independently of browser-submitted ownership fields.
Do not interpolate untrusted values into raw `ui.html`, Vue templates, JavaScript, or style content. Use wrapper text/value APIs and structured serialization. Review proxy trust, forwarded-prefix configuration, cookies, origin exposure, and WebSocket policy as deployment inputs rather than component styling concerns.
## Quality Evidence Matrix
A quality gate is satisfied by observable evidence, not by the presence of a pattern in source code.
| Concern | Required evidence |
| --- | --- |
| startup and import identity | application starts through the production entry point; reload and worker behavior match deployment; no duplicate module import paths |
| initial page response | representative pages stay within their response budget or intentionally cross `client.connected()` before long work |
| interaction correctness | primary actions, keyboard submission, validation failure, retry, duplicate action, and cancellation produce deterministic state |
| client lifecycle | disconnect/reconnect within the configured window preserves valid behavior; deletion releases page-owned resources |
| concurrency | stale reads cannot overwrite newer intent; writes have a documented conflict or idempotency policy |
| blocking behavior | concurrent-client check shows one slow action does not stall unrelated page events; profiles contain no unexpected event-loop blocking |
| storage isolation | reload, navigation, second-tab, second-user, process-restart, and multi-worker checks match each selected storage scope |
| uploads | browser rejection, direct server-side rejection, oversized request, invalid content, storage failure, and successful streaming path are distinguished |
| exception behavior | expected domain failures remain actionable; unexpected failures log tracebacks and correlation IDs; controls recover from busy state |
| cache behavior | page responses are `no-store`; public assets use deliberate versioning and lifetime; protected content is not exposed through public static routes |
| responsive layout | narrow mobile, intermediate, and wide desktop viewports show no clipping, overlap, inaccessible popup content, or layout shift from dynamic labels |
| accessibility | keyboard order, focus return, accessible names, validation association, contrast, reduced-motion behavior, and dialog/menu escape behavior are verified |
| observability | logs identify operation, client/process, route or entity, timing, and outcome without secrets or sensitive payloads |
| shutdown | timers, consumers, process/thread work, persistent storage, and external clients have deliberate cancellation or close behavior |
## Testing Surfaces
NiceGUI's [pytest integration](https://nicegui.io/documentation/section_testing) provides two complementary fixtures:
- `User` simulates interactions in Python and is the fast default for page content, component values, clicks, typing, event dispatch, navigation, and service-backed acceptance behavior.
- `Screen` drives a real headless browser and is reserved for behavior that depends on browser layout, JavaScript, actual uploads/downloads, focus, WebSockets, rendering, or client-side Quasar behavior.
Use lower-level tests for services, validation, authorization, idempotency, storage adapters, and task logic without constructing UI. Use `User` tests for application interaction contracts. Use a small set of `Screen` tests for the browser boundary, and supplement responsive or visual claims with screenshots and computed layout checks at explicit viewport sizes.
Tests should control async completion by observable state, events, or bounded timeouts rather than arbitrary sleeps. Reconnect, multi-tab, multi-user, and multi-worker behavior need dedicated environments because a single simulated client cannot establish those isolation claims.
## Source Index
!!! info "NiceGUI public documentation"
- [Pages, response timeout, connection, and multicasting](https://nicegui.io/documentation/page)
- [Error handling and execution](https://nicegui.io/documentation/section_action_events)
- [Uploads](https://nicegui.io/documentation/upload)
- [Storage](https://nicegui.io/documentation/storage)
- [Navigation](https://nicegui.io/documentation/navigate)
- [Testing](https://nicegui.io/documentation/section_testing)
- [Security guidance](https://nicegui.io/documentation/section_security)
!!! info "NiceGUI `3.16.0` implementation"
- [Page response lifecycle](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/page.py)
- [Client connection and deletion](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/client.py)
- [Outbox replay](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/outbox.py)
- [Upload route](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload.py)
- [Uploaded-file buffering](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/upload_files.py)
- [Refreshable targets](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/functions/refreshable.py)
- [Timers](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/timer.py)
- [Storage scopes](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/storage.py)
- [Navigation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/functions/navigate.py)
- [Exception and static/media handling](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/app/app.py)
- [Static cache headers](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/staticfiles.py)
!!! info "Related platform references"
- [FastAPI WebSockets](https://fastapi.tiangolo.com/advanced/websockets/)
- [FastAPI server-sent events](https://fastapi.tiangolo.com/tutorial/server-sent-events/)
- [Starlette static files](https://www.starlette.io/staticfiles/)
- [Uvicorn deployment](https://www.uvicorn.org/deployment/)