diff --git a/src/personal_mcp/docs/skills/nicegui/SKILL.md b/src/personal_mcp/docs/skills/nicegui/SKILL.md index d4dae10..daf8888 100644 --- a/src/personal_mcp/docs/skills/nicegui/SKILL.md +++ b/src/personal_mcp/docs/skills/nicegui/SKILL.md @@ -25,6 +25,7 @@ Use this skill to choose the smallest supporting reference for a NiceGUI task. T | 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 or component state with `binding.bindable_dataclass`, return a bound component handle, understand propagation and transform direction, bind nested values, avoid active-link polling, or design projection/persistence rollback | [binding dataclasses](./references/binding-dataclasses.md) | Add [application architecture](./references/architecture.md) for the render-factory and thin-page boundary or [component mechanics](./references/component-mechanics.md) for browser-originated proposals. | +| Customize `ui.table` or QTable columns, formatting, classes, props, responsive density, toolbar controls, visible columns, empty states, named slots, or frontend methods | [table customization](./references/table-customization.md) | Add [editable tables](./references/tables.md) only when cells also accept server-authoritative edits. | | 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 `@ui.refreshable` and `@ui.refreshable_method` component regions | [interaction patterns](./references/interaction-patterns.md) | Add [application architecture](./references/architecture.md) for the reusable component contract or [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. | @@ -35,6 +36,7 @@ Use this skill to choose the smallest supporting reference for a NiceGUI task. T - Use [application architecture](./references/architecture.md) for module ownership, not for page geometry or low-level component behavior. - Keep page functions thin: compose page shells and returned component handles there; keep each component's element tree, bindings, callbacks, and bounded refreshes in its render factory or component object. - 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. +- Start read-only table presentation and QTable control work in [table customization](./references/table-customization.md); keep editable state and validation in [editable tables](./references/tables.md). - 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. @@ -45,6 +47,7 @@ Load an example only when its exact mechanic matches the task: - [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. +- [table customization](./examples/table_customization.py): column defaults, dynamic formatting and classes, QTable props, responsive density, toolbar and cell slots, filtering, visible columns, and empty states. - [editable table](./examples/editable_table.py): dataframe-to-row state, named QTable cell slots, controlled editors, dialog-based whole-row save/cancel edits, Python validation, touched rows, and canonical row refresh. ## Defaults That Span References @@ -55,7 +58,8 @@ Load an example only when its exact mechanic matches the task: - Avoid blocking I/O and CPU-heavy work in the UI event loop. - Prefer event-driven updates and explicit refreshes to unrelated polling. - Keep browser-originated values as proposals; validate and normalize them in Python before mutating authoritative state. -- Use Tailwind for physical structure and scoped static CSS only for requirements that NiceGUI, Quasar, or utilities cannot express cleanly. +- Prefer NiceGUI context managers and `ui.*` elements over raw Vue templates. Keep application logic and authoritative state in Python; use minimal browser expressions only for scoped-slot values or client-only behavior, following [Python-owned slot composition](./references/component-mechanics.md#prefer-python-owned-composition). +- Use NiceGUI context managers for element structure and Tailwind for generic layout, spacing, responsive behavior, and typography. Keep Quasar classes for semantic palette roles or component-specific geometry, and use Quasar props for component behavior and density; see [combining Tailwind with Quasar utilities](./references/styling-and-customization.md#combine-tailwind-with-quasar-utilities-deliberately). - Provide loading, success, and failure states for user-triggered work. - Verify component-specific behavior against the installed NiceGUI version and its bundled Quasar version. diff --git a/src/personal_mcp/docs/skills/nicegui/examples/table_customization.py b/src/personal_mcp/docs/skills/nicegui/examples/table_customization.py new file mode 100755 index 0000000..fca39b3 --- /dev/null +++ b/src/personal_mcp/docs/skills/nicegui/examples/table_customization.py @@ -0,0 +1,158 @@ +#!/usr/bin/env -S uv run --script +# /// script +# dependencies = [ +# "nicegui==3.16.0", +# ] +# /// + +from nicegui import ui + +type TableValue = str | int | float +type TableRow = dict[str, TableValue] + +STATUS_COLORS = { + "Ready": "positive", + "Low": "warning", + "Backorder": "negative", +} + +COLUMNS = [ + { + "name": "name", + "label": "Product", + "field": "name", + "required": True, + "sortable": True, + "align": "left", + "headerClasses": "bg-grey-2 text-grey-9 font-bold", + "classes": "font-medium", + "headerStyle": "width: 40%; min-width: 12rem", + "style": "width: 40%; min-width: 12rem", + }, + { + "name": "category", + "label": "Category", + "field": "category", + "sortable": True, + "align": "left", + "headerStyle": "width: 9rem", + "style": "width: 9rem", + }, + { + "name": "stock", + "label": "In stock", + "field": "stock", + "sortable": True, + "align": "right", + "headerStyle": "width: 7rem", + "style": "width: 7rem", + ":classes": "row => row.stock < 10 ? 'text-negative font-bold' : ''", + }, + { + "name": "price", + "label": "Unit price", + "field": "price", + "sortable": True, + "align": "right", + "headerStyle": "width: 8rem", + "style": "width: 8rem", + ":format": "value => `$${value.toFixed(2)}`", + }, + { + "name": "status", + "label": "Status", + "field": "status", + "sortable": True, + "align": "center", + "headerStyle": "width: 8rem", + "style": "width: 8rem", + "colorByValue": STATUS_COLORS, + }, +] + +ROWS: list[TableRow] = [ + {"id": 101, "name": "Desk lamp", "category": "Lighting", "stock": 7, "price": 42.5, "status": "Low"}, + {"id": 102, "name": "Task chair", "category": "Seating", "stock": 18, "price": 289.0, "status": "Ready"}, + {"id": 103, "name": "Monitor arm", "category": "Hardware", "stock": 0, "price": 119.95, "status": "Backorder"}, + {"id": 104, "name": "Cable tray", "category": "Hardware", "stock": 34, "price": 31.25, "status": "Ready"}, + {"id": 105, "name": "Side table", "category": "Furniture", "stock": 9, "price": 164.5, "status": "Low"}, + {"id": 106, "name": "Floor light", "category": "Lighting", "stock": 15, "price": 98.0, "status": "Ready"}, +] + + +def render_table() -> ui.table: + table = ui.table( + columns=COLUMNS, + column_defaults={"headerClasses": "text-grey-8"}, + rows=ROWS, + row_key="id", + pagination={ + "sortBy": "stock", + "descending": True, + "rowsPerPage": 5, + }, + ).classes("w-full max-w-5xl") + + ( + table.props( + # Surface and cell layout. + "flat bordered separator=horizontal wrap-cells" + ) + .props( + # Local sorting and page-size choices; zero means "all rows". + 'binary-state-sort :rows-per-page-options="[5, 10, 0]"' + ) + .props( + # Compact cells only below Quasar's medium breakpoint. + ':dense="Quasar.Screen.lt.md"' + ) + .props( + # Distinguish an empty dataset from a filter with no matches. + 'no-data-label="No inventory items" no-results-label="No matching inventory items"' + ) + ) + + with table.add_slot("top-left"), ui.row().classes("items-center gap-2"): + ui.icon("inventory_2", size="sm").classes("text-primary") + ui.label("Inventory").classes("text-xl font-medium") + ui.badge(str(len(ROWS)), color="grey-3").props("text-color=grey-9") + + with table.add_slot("top-right"): + ui.input(placeholder="Search inventory").props("dense outlined clearable debounce=250").bind_value_to( + table, + "filter", + ) + + with table.add_slot("body-cell-status"), table.cell("status"): + ui.badge().props(':label="props.value" :color="props.col.colorByValue[props.value] ?? \'grey\'" outline') + + with table.add_slot("no-data"), ui.row().classes("w-full items-center justify-center gap-2 p-6 text-grey-7"): + ui.icon("inventory_2", size="2em").props(":name=\"props.filter ? 'filter_alt_off' : 'inventory_2'\"") + ui.element("span").props(':textContent="props.message"') + + optional_columns = [column for column in table.columns if not column.get("required")] + + def set_visible_columns(names: list[str]) -> None: + visible = set(names) + for column in optional_columns: + hidden = column["name"] not in visible + column["classes"] = "hidden" if hidden else "" + column["headerClasses"] = "hidden" if hidden else "text-grey-8" + table.update() + + ui.select( + {column["name"]: column["label"] for column in optional_columns}, + value=[column["name"] for column in optional_columns], + label="Visible columns", + multiple=True, + on_change=lambda event: set_visible_columns(event.value), + ).props("outlined dense options-dense").classes("w-64") + + return table + + +if __name__ in {"__main__", "__mp_main__"}: + with ui.column().classes("w-full items-center gap-4 p-4"): + render_table() + + ui.run(port=8888, reload=True) diff --git a/src/personal_mcp/docs/skills/nicegui/references/component-mechanics.md b/src/personal_mcp/docs/skills/nicegui/references/component-mechanics.md index c70ffd9..af6e05f 100644 --- a/src/personal_mcp/docs/skills/nicegui/references/component-mechanics.md +++ b/src/personal_mcp/docs/skills/nicegui/references/component-mechanics.md @@ -302,6 +302,14 @@ NiceGUI creates a default slot for every element. Entering an element as a conte These mechanics are defined by the tagged [`Element.add_slot()` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/element.py), the [`Slot` context manager](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/slot.py), and NiceGUI's [context-managed scoped-slot examples](https://github.com/zauberzeug/nicegui/blob/v3.16.0/website/documentation/content/table_documentation.py). +### Prefer Python-Owned Composition + +Use NiceGUI context managers and `ui.*` elements for slot structure whenever they can represent the required element tree. Keep values, mappings, validation, permissions, event handling, and authoritative state transitions in Python. This preserves element identity, typed wrapper APIs, lifecycle cleanup, test visibility, and the normal NiceGUI update path. + +Use the narrowest browser-side expression for state that exists only while Quasar renders a scoped slot. A dynamic prop such as `:label="props.value"` may project that value into a NiceGUI element without moving the surrounding structure or business rules into JavaScript. When Python needs a browser-owned value, emit the smallest serializable proposal to a Python handler and validate it there. + +Escalate to `add_slot(name, template)` only when the slot contract requires client-side structure that context-managed NiceGUI elements cannot preserve, such as a browser-side `v-for`, a variable number of sibling roots, or Vue's object form of `v-bind` for a Quasar interaction bundle. Keep raw templates small, use documented scoped props, and do not duplicate authoritative application logic in JavaScript. + ### Context-Managed NiceGUI Elements Ordinary NiceGUI elements can populate slot content: @@ -313,7 +321,7 @@ with name_input.add_slot("prepend"): ui.icon("person") ``` -Nested context managers express the component hierarchy while preserving NiceGUI element identity, event registration, updates, deletion, and test visibility. The `add_slot(name, template)` form accepts a raw Vue template for client-side structures such as a `v-for` that creates a variable number of sibling elements. +Nested context managers express the component hierarchy while preserving NiceGUI element identity, event registration, updates, deletion, and test visibility. ### Scoped Props On The Client diff --git a/src/personal_mcp/docs/skills/nicegui/references/styling-and-customization.md b/src/personal_mcp/docs/skills/nicegui/references/styling-and-customization.md index 7136ce6..76ef078 100644 --- a/src/personal_mcp/docs/skills/nicegui/references/styling-and-customization.md +++ b/src/personal_mcp/docs/skills/nicegui/references/styling-and-customization.md @@ -82,7 +82,20 @@ Tailwind's [responsive variants](https://tailwindcss.com/docs/responsive-design) ### Combine Tailwind With Quasar Utilities Deliberately -NiceGUI's `.classes()` accepts both Tailwind utilities and CSS helpers bundled with Quasar. Tailwind remains the default for application layout and responsive structure; Quasar helpers are useful when dimensions should follow Quasar's component conventions: +NiceGUI's `.classes()` accepts both Tailwind utilities and CSS helpers bundled with Quasar. Assign each concern to one system: + +| Concern | Default owner | Examples | +| --- | --- | --- | +| Python element structure | NiceGUI | `ui.row`, `ui.column`, `ui.grid`, slot context managers | +| Generic geometry and responsive layout | Tailwind | `w-full`, `items-center`, `justify-center`, `gap-2`, `p-6`, `flex-wrap` | +| Generic application typography | Tailwind | `text-xl`, `font-medium`, `leading-6`, `truncate` | +| Theme-aware semantic color | Quasar | `text-primary`, `bg-positive`, `text-grey-7` | +| Component behavior and density | Quasar props | `dense`, `outlined`, `round`, `separator=horizontal` | +| Component-specific geometry | Quasar helper classes | `q-table--col-auto-width`, `absolute-top-right` | + +Prefer the Tailwind spelling when both systems express ordinary application layout or typography. For example, use `w-full` instead of `full-width`, `items-center justify-center` instead of `flex-center`, `gap-2` instead of `q-gutter-sm`, `p-4` instead of `q-pa-md`, and `text-xl font-medium` instead of `text-h6 text-weight-medium`. This keeps spacing, breakpoints, and type choices in one vocabulary. + +Quasar helpers remain useful when a value intentionally follows Quasar's component conventions: - [`q-m*` and `q-p*` spacing classes](https://quasar.dev/style/spacing) when spacing should follow Quasar's component scale - [typography helpers](https://quasar.dev/style/typography), such as `text-h6`, `text-subtitle2`, and `text-weight-medium`, for text that should follow Quasar's type system @@ -90,7 +103,7 @@ NiceGUI's `.classes()` accepts both Tailwind utilities and CSS helpers bundled w - [positioning helpers](https://quasar.dev/style/positioning), such as `absolute-top-right`, when positioning content relative to a Quasar component - [size and overflow helpers](https://quasar.dev/style/other-helper-classes), such as `fit`, `full-width`, and `overflow-auto`, when matching Quasar layout behavior -Do not assign the same property through both systems on one element. For example, `w-full q-pa-md` uses Tailwind for width and Quasar for component-scale padding; adding `p-4` would create competing padding declarations. The same rule applies to Tailwind and Quasar visibility helpers or to Tailwind font sizes and Quasar heading classes. +Do not assign the same property through both systems on one element. For example, `w-full q-pa-md` deliberately uses Tailwind for width and Quasar for component-scale padding; adding `p-4` would create competing padding declarations. The same rule applies to Tailwind and Quasar visibility helpers or to Tailwind font sizes and Quasar heading classes. ```python with ui.card().classes("w-full max-w-2xl q-pa-md"): diff --git a/src/personal_mcp/docs/skills/nicegui/references/table-customization.md b/src/personal_mcp/docs/skills/nicegui/references/table-customization.md new file mode 100644 index 0000000..55f355e --- /dev/null +++ b/src/personal_mcp/docs/skills/nicegui/references/table-customization.md @@ -0,0 +1,280 @@ +# Table Customization + +Use this reference when a [`ui.table`](https://nicegui.io/documentation/table) needs presentation, controls, responsive behavior, or custom cell content while retaining [Quasar QTable](https://quasar.dev/vue-components/table) sorting, filtering, pagination, and selection behavior. Use [editable tables](./tables.md) instead when browser-originated cell values must be validated and committed by Python. + +## Version Baseline + +This reference and its runnable example were verified against this bundled stack: + +| Layer | Version | Evidence | +| --- | --- | --- | +| NiceGUI | `3.16.0` | [Tagged `Table` source](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/table.py) | +| Quasar | `2.18.5` | [NiceGUI frontend dependencies](https://github.com/zauberzeug/nicegui/blob/v3.16.0/package.json) | +| Vue | `3.5.22` | [NiceGUI frontend dependencies](https://github.com/zauberzeug/nicegui/blob/v3.16.0/package.json) | + +Recheck the tagged NiceGUI sources and bundled dependencies for another release. The current Quasar documentation may describe features added after NiceGUI's bundled Quasar version. + +## Choose The Narrowest Layer + +Apply customization at the highest-level API that expresses it: + +1. Use `ui.table(...)` for rows, columns, defaults, stable row identity, title, selection, and pagination. +2. Use column definitions for alignment, sorting, formatting, cell classes, header classes, and width hints. +3. Use `.props(...)` for QTable behavior that the NiceGUI constructor does not expose, such as `dense`, `separator`, `wrap-cells`, `rows-per-page-options`, and empty-state labels. +4. Use named slots for custom toolbar content, one special header or cell, loading, no-data content, or pagination controls. +5. Use a full `header`, `body`, or `item` slot only when the whole generated structure must change. +6. Add narrowly scoped CSS for behavior that neither component API covers, such as sticky columns. + +NiceGUI's tagged [table client wrapper](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/table.js) passes props to QTable and forwards every supplied slot with its scoped properties. This makes QTable's API the source of truth below the NiceGUI wrapper, but it does not make every current QTable feature compatible with the bundled `2.18.5` release. + +## Columns Before Slots + +A QTable column is both a data projection and a presentation contract. Keep its `name` unique because sorting and `body-cell-[name]` slot selection use it. Keep `field` separate when the displayed column name differs from the row key. + +```python +columns = [ + { + "name": "price", + "label": "Unit price", + "field": "price", + "sortable": True, + "align": "right", + ":format": "value => `$${value.toFixed(2)}`", + }, +] + +table = ui.table( + rows=rows, + columns=columns, + column_defaults={"headerClasses": "text-grey-8"}, + row_key="id", +) +``` + +Use plain keys such as `classes`, `style`, `headerClasses`, and `headerStyle` for static values. NiceGUI's client-side dynamic-property conversion recognizes colon-prefixed keys such as `:field`, `:format`, `:sort`, `:classes`, and `:style` as JavaScript expressions. Keep row data JSON-serializable; format display values in a column or slot rather than putting component objects in rows. + +### Cell Classes And Styles + +Quasar's tagged [`table-column-selection.js`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/ui/src/components/table/table-column-selection.js), [`QTh.js`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/ui/src/components/table/QTh.js), and [`QTd.js`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/ui/src/components/table/QTd.js) establish the exact targets: + +| Column key | Applied to | Static or dynamic | Typical uses | +| --- | --- | --- | --- | +| `headerClasses` | The generated `