# millwork 0.1.6 reference A page is a Python function of the session's state that returns a tree of components. A handler bound to an `on_*` prop takes `(session, payload)` and may be sync or async; the page renders again when it returns. Each handler below says what its payload is. Children are positional: `ui.Card(ui.Text('a'), ui.Button('b'), title='t')`. Every component takes `key`, `hidden` and `cls` (Tailwind classes). A prop a component does not have is refused, and so is an icon name lucide does not have. The manual at millwork-ui.com explains each chapter; this is the table of what exists. ## Components ### ui.Accordion `ui.Accordion(*children, **props)` - `multiple: bool` = `False` - `value: list[str] | None` = `None` - `default: list[str] | None` = `None` - `on_change`: handler `(session, payload)`, payload the open items' values ### ui.AccordionItem `ui.AccordionItem(*children, **props)` - `id: str` (required) - `title: str` (required) ### ui.Alert `ui.Alert(*children, **props)` - `title: str | None` = `None` - `description: str | None` = `None` - `tone: 'default' | 'muted' | 'primary' | 'accent' | 'success' | 'warning' | 'destructive'` = `'default'` - `icon: str | None` = `None` - `dismissable: bool` = `False` - `actions: list[Component]` = `[]` - `on_dismiss`: handler `(session, payload)`, payload None ### ui.AreaChart Lines with the space under them filled; ``stacked`` adds the series up. `ui.AreaChart(rows=(), *children, **props)` - `rows: Sequence[Any]` = `()` - `x: str` (required) - `y: Any` (required) - `by: str | None` = `None` - `format: 'number' | 'integer' | 'percent' | 'currency' | None` = `None` - `labels: bool` = `False` - `legend: bool | None` = `None` - `emphasis: str | None` = `None` - `colors: dict[str, int] | None` = `None` - `height: int | None` = `None` - `on_click`: handler `(session, payload)`, payload {'x': category, 'series': key, 'value': n, 'values': {series: n}}; series and value are None for a click beside the marks of a line or area - `stacked: bool | 'percent'` = `False` - `curve: bool` = `False` ### ui.BarChart Bars for magnitudes by category: grouped, ``stacked``, ``horizontal``. `ui.BarChart(rows=(), *children, **props)` - `rows: Sequence[Any]` = `()` - `x: str` (required) - `y: Any` (required) - `by: str | None` = `None` - `format: 'number' | 'integer' | 'percent' | 'currency' | None` = `None` - `labels: bool` = `False` - `legend: bool | None` = `None` - `emphasis: str | None` = `None` - `colors: dict[str, int] | None` = `None` - `height: int | None` = `None` - `on_click`: handler `(session, payload)`, payload {'x': category, 'series': key, 'value': n, 'values': {series: n}}; series and value are None for a click beside the marks of a line or area - `stacked: bool | 'percent'` = `False` - `horizontal: bool` = `False` - `diverging: bool` = `False` ### ui.PieChart Shares of a whole: one slice per row, ``x`` naming the slice and ``y`` the value. Slices take ``chart-1`` to ``chart-5`` in row order, or the slot ``colors`` names for them. Over five, the smallest fold into one slice named by ``other``, drawn in grey; with ``other=None`` a sixth row is refused instead. ``donut`` leaves the middle open and ``center`` writes a figure there. ``labels`` writes each slice's share beside it. `ui.PieChart(rows=(), *children, **props)` - `rows: Sequence[Any]` = `()` - `x: str` (required) - `y: str` (required) - `format: 'number' | 'integer' | 'percent' | 'currency' | None` = `None` - `labels: bool` = `False` - `legend: bool | None` = `None` - `donut: bool` = `False` - `center: str | None` = `None` - `other: str | None` = `'Other'` - `colors: dict[str, int] | None` = `None` - `height: int | None` = `None` - `on_click`: handler `(session, payload)`, payload {'x': slice, 'value': number, 'share': fraction of the total, or None} ### ui.Badge `ui.Badge(label='', *children, **props)` - `label: str` = `''` - `tone: 'default' | 'muted' | 'primary' | 'accent' | 'success' | 'warning' | 'destructive'` = `'default'` - `icon: str | None` = `None` ### ui.Button `ui.Button(label='', *children, **props)` - `label: str` = `''` - `variant: 'primary' | 'secondary' | 'outline' | 'ghost' | 'link' | 'destructive'` = `'primary'` - `size: 'sm' | 'md' | 'lg'` = `'md'` - `icon: str | None` = `None` - `icon_position: 'start' | 'end'` = `'start'` - `disabled: bool` = `False` - `loading: bool` = `False` - `tooltip: str | None` = `None` - `confirm: str | None` = `None` - `form: str | None` = `None` - `on_click`: handler `(session, payload)`, payload None ### ui.Card `ui.Card(*children, **props)` - `title: str | None` = `None` - `description: str | None` = `None` - `footer: Component | None` = `None` - `padding: bool` = `True` - `tone: 'default' | 'muted' | 'primary' | 'accent' | 'success' | 'warning' | 'destructive' | None` = `None` - `on_click`: handler `(session, payload)`, payload None ### ui.Chat `ui.Chat(*children, **props)` - `welcome: str | None` = `None` - `suggestions: list[str]` = `[]` - `placeholder: str | None` = `None` - `attachments: Accept | None` = `None` - `max_chars: int` = `16000` - `max_messages: int` = `200` - `commands: list[Command]` = `[]` - `disabled: bool` = `False` - `show_steps: 'full' | 'collapsed' | 'hidden'` = `'collapsed'` - `avatar: str | None` = `None` - `assistant_name: str | None` = `None` - `names: bool` = `False` - `timestamps: bool` = `False` - `durations: bool` = `False` - `copyable: bool` = `True` - `regenerate: bool` = `True` - `edit: bool` = `True` - `actions: list[Action]` = `[]` - `tools: list[Component]` = `[]` - `on_message`: handler `(session, payload)`, payload the ChatTurn: its text, files and reply() - `on_stop`: handler `(session, payload)`, payload None - `on_feedback`: handler `(session, message, value, comment)`, arguments the message, 'up', 'down' or None when withdrawn, and the comment - `on_action`: handler `(session, message, action)`, arguments the message and the action's id ### ui.Checkbox `ui.Checkbox(*children, **props)` - `label: str | None` = `None` - `description: str | None` = `None` - `checked: bool | None` = `None` - `default_checked: bool | None` = `None` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload True when checked ### ui.Code `ui.Code(content='', *children, **props)` - `content: str` = `''` - `language: str | None` = `None` - `title: str | None` = `None` - `copyable: bool` = `True` - `wrap: bool` = `False` - `max_height: str | None` = `None` ### ui.CheckboxGroup `ui.CheckboxGroup(*children, **props)` - `options: list[Option]` (required) - `value: list[str] | None` = `None` - `default: list[str] | None` = `None` - `label: str | None` = `None` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the checked values ### ui.Collapsible `ui.Collapsible(*children, **props)` - `title: str` (required) - `open: bool | None` = `None` - `default_open: bool | None` = `None` - `on_toggle`: handler `(session, payload)`, payload True when open ### ui.Custom An app's own component: ``components/.jsx``, compiled in the tab. `ui.Custom(name, *children, **props)` - `name: str` (required) - `props: dict[str, Any]` = `{}` - `events: dict[str, Any]` = `{}` - `on_event`: handler `(session, payload)`, payload (name, payload) as the component emitted it ### ui.Dialog ``open`` is always server-owned: a dialog is state the app reasons about. `ui.Dialog(*children, **props)` - `open: bool` = `False` - `title: str | None` = `None` - `description: str | None` = `None` - `footer: Component | None` = `None` - `size: 'sm' | 'md' | 'lg' | 'xl' | 'full'` = `'md'` - `dismissable: bool` = `True` - `on_close`: handler `(session, payload)`, payload None ### ui.Download A button that downloads a file, bytes, text, or what a callable returns on click. `ui.Download(label='Download', *children, **props)` - `label: str` = `'Download'` - `filename: str` (required) - `content: Any` (required) - `mime: str | None` = `None` - `variant: 'primary' | 'secondary' | 'outline' | 'ghost' | 'link'` = `'outline'` - `icon: str | None` = `'download'` - `disabled: bool` = `False` ### ui.FileCard `ui.FileCard(*children, **props)` - `file: FileRef | None` = `None` - `name: str | None` = `None` - `size: int | None` = `None` - `kind: str | None` = `None` - `url: str | None` = `None` - `status: 'ready' | 'loading' | 'error'` = `'ready'` - `meta: str | None` = `None` - `actions: list[MenuItem]` = `[]` - `on_action`: handler `(session, payload)`, payload the chosen action's id - `on_click`: handler `(session, payload)`, payload None ### ui.FileUpload Uploads go over the session's socket; the handler receives ``FileRef``s. `ui.FileUpload(*children, **props)` - `accept: list[str] | None` = `None` - `multiple: bool` = `False` - `max_files: int` = `10` - `max_size_mb: int | None` = `None` - `label: str | None` = `None` - `description: str | None` = `None` - `dropzone: bool` = `True` - `disabled: bool` = `False` - `on_upload`: handler `(session, payload)`, payload the FileRefs - `on_reject`: handler `(session, payload)`, payload {'name': filename, 'reason': why} ### ui.Empty `ui.Empty(*children, **props)` - `title: str` (required) - `description: str | None` = `None` - `icon: str | None` = `None` - `action: Component | None` = `None` ### ui.Form `ui.Form(model=None, *children, **props)` - `model: type[BaseModel]` = `None` - `initial: Any` = `None` - `submit: str | None` = `'Save'` - `cancel: str | None` = `None` - `layout: 'vertical' | 'horizontal' | int` = `'vertical'` - `fields: list[str] | None` = `None` - `labels: Mapping[str, Mapping[str, Any]] | None` = `None` - `overrides: Mapping[str, Mapping[str, Any]] | None` = `None` - `issues: list[Issue]` = `[]` - `disabled: bool` = `False` - `read_only: bool` = `False` - `dense: bool` = `False` - `on_submit`: handler `(session, payload)`, payload the validated model instance - `on_cancel`: handler `(session, payload)`, payload None ### ui.Grid `ui.Grid(*children, **props)` - `cols: int | dict[str, int]` = `2` - `gap: int` = `4` ### ui.Header The app bar: the theme's logo, a title, links, the page's actions (children) and the user menu with the theme choice and sign-out. `ui.Header(*children, **props)` - `title: str | None` = `None` - `logo: bool` = `True` - `links: list[Link]` = `[]` - `user_menu: bool` = `True` ### ui.Image `ui.Image(*children, **props)` - `src: FileRef | str` (required) - `alt: str` = `''` - `size: 'sm' | 'md' | 'lg' | 'full'` = `'md'` - `fit: 'cover' | 'contain'` = `'contain'` - `on_click`: handler `(session, payload)`, payload None ### ui.Input `ui.Input(*children, **props)` - `value: str | None` = `None` - `default: str | None` = `None` - `placeholder: str | None` = `None` - `kind: 'text' | 'password' | 'email' | 'number' | 'search' | 'url'` = `'text'` - `prefix_icon: str | None` = `None` - `suffix: str | None` = `None` - `disabled: bool` = `False` - `readonly: bool` = `False` - `clearable: bool` = `False` - `debounce_ms: int` = `250` - `on_change`: handler `(session, payload)`, payload the text - `on_submit`: handler `(session, payload)`, payload the text, on Enter ### ui.Kbd `ui.Kbd(*children, **props)` - `keys: list[str]` (required) ### ui.LineChart Lines over an ordered x: one per series, a crosshair tooltip across them. `ui.LineChart(rows=(), *children, **props)` - `rows: Sequence[Any]` = `()` - `x: str` (required) - `y: Any` (required) - `by: str | None` = `None` - `format: 'number' | 'integer' | 'percent' | 'currency' | None` = `None` - `labels: bool` = `False` - `legend: bool | None` = `None` - `emphasis: str | None` = `None` - `colors: dict[str, int] | None` = `None` - `height: int | None` = `None` - `on_click`: handler `(session, payload)`, payload {'x': category, 'series': key, 'value': n, 'values': {series: n}}; series and value are None for a click beside the marks of a line or area - `curve: bool` = `False` - `dots: bool` = `False` ### ui.Link `ui.Link(*children, **props)` - `label: str` (required) - `href: str` (required) - `target: '_self' | '_blank'` = `'_self'` - `icon: str | None` = `None` - `active: bool` = `False` ### ui.Markdown `ui.Markdown(content='', *children, **props)` - `content: str` = `''` - `tight: bool` = `False` ### ui.NumberInput `ui.NumberInput(*children, **props)` - `value: float | None` = `None` - `default: float | None` = `None` - `min: float | None` = `None` - `max: float | None` = `None` - `step: float | None` = `None` - `suffix: str | None` = `None` - `placeholder: str | None` = `None` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the number, or None when cleared ### ui.Page The root of a page: a column of its children with ``gap`` between them, under a ``header``, beside a ``Sidebar`` child when there is one. `ui.Page(*children, **props)` - `title: str | None` = `None` - `header: Header | None` = `None` - `padding: bool` = `True` - `gap: int` = `6` - `max_width: str | None` = `None` ### ui.Pdf A PDF in the browser's own viewer. A form or a link inside the document looks like part of the app, so show files the app made or the person uploaded themselves, not documents from strangers. `ui.Pdf(*children, **props)` - `src: FileRef | str` (required) - `page: int | None` = `None` - `height: str` = `'70vh'` ### ui.Progress A bar; with ``task`` the value, label and message come from the task. `ui.Progress(*children, **props)` - `value: float | None` = `None` - `label: str | None` = `None` - `description: str | None` = `None` - `task: Any` = `None` ### ui.RadioGroup `ui.RadioGroup(*children, **props)` - `options: list[Option]` (required) - `value: str | None` = `None` - `default: str | None` = `None` - `label: str | None` = `None` - `orientation: 'vertical' | 'horizontal'` = `'vertical'` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the chosen value ### ui.Row `ui.Row(*children, **props)` - `gap: int` = `4` - `align: 'start' | 'center' | 'end' | 'stretch' | 'baseline'` = `'center'` - `justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'` = `'start'` - `wrap: bool` = `False` ### ui.ScrollArea `ui.ScrollArea(*children, **props)` - `height: str | None` = `None` ### ui.Select `ui.Select(*children, **props)` - `options: list[Option]` (required) - `value: str | None` = `None` - `default: str | None` = `None` - `placeholder: str | None` = `None` - `clearable: bool` = `False` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the chosen value, or None ### ui.Field A label, help text and an error around one control, for a form built by hand. `ui.Field(*children, **props)` - `label: str` (required) - `help: str | None` = `None` - `error: str | None` = `None` - `required: bool` = `False` ### ui.DropdownMenu A menu behind a trigger button. ``on_select`` receives the item's id. `ui.DropdownMenu(*children, **props)` - `trigger: Component` (required) - `items: list[MenuItem]` (required) - `align: 'start' | 'end'` = `'start'` - `on_select`: handler `(session, payload)`, payload the chosen item's id ### ui.ButtonGroup Buttons joined into one control. `ui.ButtonGroup(*children, **props)` ### ui.DatePicker A date, or with ``range`` a ``[start, end]`` pair, as ISO strings (``date`` objects are accepted and converted). ``min`` and ``max`` bound what can be picked. `ui.DatePicker(*children, **props)` - `value: str | list[str | None] | None` = `None` - `default: str | list[str | None] | None` = `None` - `min: str | None` = `None` - `max: str | None` = `None` - `range: bool` = `False` - `placeholder: str | None` = `None` - `clearable: bool` = `False` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the ISO date, or [start, end] for a range, or None ### ui.MultiSelect Several choices from a list, shown as chips; typing filters the list. `ui.MultiSelect(*children, **props)` - `options: list[Option]` (required) - `value: list[str] | None` = `None` - `default: list[str] | None` = `None` - `placeholder: str | None` = `None` - `searchable: bool` = `True` - `max: int | None` = `None` - `sortable: bool` = `False` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the chosen values, in order ### ui.Separator `ui.Separator(*children, **props)` - `orientation: 'horizontal' | 'vertical'` = `'horizontal'` - `label: str | None` = `None` ### ui.Sheet `ui.Sheet(*children, **props)` - `open: bool` = `False` - `side: 'left' | 'right' | 'top' | 'bottom'` = `'right'` - `title: str | None` = `None` - `description: str | None` = `None` - `on_close`: handler `(session, payload)`, payload None ### ui.Sidebar A collapsible side panel. ``Page`` with a ``Sidebar`` child lays out around it. `ui.Sidebar(*children, **props)` - `side: 'left' | 'right'` = `'left'` - `width: str | None` = `None` - `collapsible: 'offcanvas' | 'icon' | 'none'` = `'offcanvas'` - `open: bool | None` = `None` - `default_open: bool | None` = `None` - `title: str | None` = `None` - `on_toggle`: handler `(session, payload)`, payload True when open ### ui.Skeleton `ui.Skeleton(*children, **props)` - `rows: int` = `3` - `shape: 'text' | 'card' | 'table'` = `'text'` ### ui.Slider `ui.Slider(*children, **props)` - `min: float` = `0` - `max: float` = `100` - `step: float` = `1` - `value: float | None` = `None` - `default: float | None` = `None` - `show_value: bool` = `True` - `marks: list[float] | dict[float, str] | None` = `None` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload the number ### ui.Spinner `ui.Spinner(*children, **props)` - `size: 'sm' | 'md' | 'lg'` = `'md'` - `label: str | None` = `None` ### ui.Split Resizable panes. Sizes are percentages; the user's drag is client-owned. `ui.Split(*children, **props)` - `sizes: list[int] | None` = `None` - `min_sizes: list[int] | None` = `None` - `direction: 'horizontal' | 'vertical'` = `'horizontal'` - `collapsible: bool | list[bool]` = `False` ### ui.Stack `ui.Stack(*children, **props)` - `gap: int` = `4` - `align: 'start' | 'center' | 'end' | 'stretch' | 'baseline'` = `'stretch'` - `justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'` = `'start'` - `grow: bool` = `False` - `divided: bool` = `False` ### ui.Stat A headline number. ``delta`` is the change, shown with its direction and coloured by whether up is ``good``; ``trend`` draws a sparkline. `ui.Stat(*children, **props)` - `label: str` (required) - `value: str` (required) - `hint: str | None` = `None` - `tone: 'default' | 'muted' | 'primary' | 'accent' | 'success' | 'warning' | 'destructive' | None` = `None` - `icon: str | None` = `None` - `delta: float | None` = `None` - `delta_format: 'percent' | 'number'` = `'percent'` - `good: 'up' | 'down'` = `'up'` - `trend: list[float] | None` = `None` ### ui.Steps A step tree on Trace: the task's steps, or ``items`` given directly. `ui.Steps(*children, **props)` - `items: list[Any] | None` = `None` - `task: Any` = `None` - `compact: bool` = `False` ### ui.Switch `ui.Switch(*children, **props)` - `label: str | None` = `None` - `description: str | None` = `None` - `checked: bool | None` = `None` - `default_checked: bool | None` = `None` - `disabled: bool` = `False` - `on_change`: handler `(session, payload)`, payload True when on ### ui.Tab `ui.Tab(*children, **props)` - `id: str` (required) - `label: str` (required) - `icon: str | None` = `None` - `badge: str | None` = `None` - `disabled: bool` = `False` ### ui.Table `ui.Table(*children, **props)` - `rows: Sequence[Any]` = `[]` - `columns: list[Column] | None` = `None` - `row_key: str` = `'id'` - `height: int | str | None` = `None` - `sticky_header: bool` = `True` - `sortable: bool` = `True` - `default_sort: list[tuple[str, 'asc' | 'desc']]` = `[]` - `filters: bool` = `False` - `search: bool` = `False` - `page_size: int | None` = `None` - `selection: 'none' | 'row' | 'rows'` = `'none'` - `selected: list[Any]` = `[]` - `row_actions: list[MenuItem]` = `[]` - `tones: callable | Mapping[str, callable] | None` = `None` - `summary: bool | str` = `True` - `empty: str` = `''` - `on_select`: handler `(session, payload)`, payload the selected row keys - `on_row_click`: handler `(session, payload)`, payload the row's key - `on_action`: handler `(session, payload)`, payload [action_id, row_key] - `on_sort`: handler `(session, payload)`, payload [[column, 'asc' | 'desc'], ...] - `on_filter`: handler `(session, payload)`, payload {'columns': {column: value}, 'search': text} ### ui.Tabs `ui.Tabs(*children, **props)` - `value: str | None` = `None` - `default: str | None` = `None` - `variant: 'line' | 'pill'` = `'line'` - `on_change`: handler `(session, payload)`, payload the chosen tab's value ### ui.Text `ui.Text(content='', *children, **props)` - `content: str` = `''` - `variant: 'body' | 'muted' | 'caption' | 'label' | 'title' | 'heading' | 'subheading' | 'mono'` = `'body'` - `truncate: bool` = `False` - `align: 'left' | 'center' | 'right' | None` = `None` - `tone: 'default' | 'muted' | 'primary' | 'accent' | 'success' | 'warning' | 'destructive' | None` = `None` ### ui.Logo The theme's logo, the light or dark file with the scheme; nothing when the theme has none. For a page outside the app bar: a login-like page, an error page. `ui.Logo(*children, **props)` ### ui.Textarea `ui.Textarea(*children, **props)` - `value: str | None` = `None` - `default: str | None` = `None` - `placeholder: str | None` = `None` - `rows: int` = `3` - `autosize: bool` = `False` - `max_length: int | None` = `None` - `disabled: bool` = `False` - `readonly: bool` = `False` - `debounce_ms: int` = `250` - `on_change`: handler `(session, payload)`, payload the text - `on_submit`: handler `(session, payload)`, payload the text, on Ctrl+Enter ### ui.ToggleGroup `ui.ToggleGroup(*children, **props)` - `options: list[Option]` (required) - `value: str | list[str] | None` = `None` - `default: str | list[str] | None` = `None` - `multiple: bool` = `False` - `size: 'sm' | 'md' | 'lg'` = `'md'` - `on_change`: handler `(session, payload)`, payload the chosen value, a list when multiple ### ui.Tooltip `ui.Tooltip(*children, **props)` - `content: str` (required) ## Models components take ### ui.Option One choice in a Select, RadioGroup, CheckboxGroup, ToggleGroup. An ``options`` list also takes plain strings, ``(value, label)`` pairs and dicts. - `value: str` (required) - `label: str | None` = `None` - `description: str | None` = `None` - `icon: str | None` = `None` - `disabled: bool` = `False` - `group: str | None` = `None` ### ui.Column - `key: str` (required) - `header: str | None` = `None` - `width: int | str | None` = `None` - `align: 'left' | 'center' | 'right'` = `'left'` - `format: 'text' | 'number' | 'integer' | 'percent' | 'currency' | 'date' | 'datetime' | 'bool' | 'badge' | 'link' | 'code'` = `'text'` - `sortable: bool` = `True` - `filterable: bool` = `True` - `hidden: bool` = `False` - `wrap: bool` = `False` ### ui.MenuItem - `id: str` (required) - `label: str` (required) - `icon: str | None` = `None` - `disabled: bool` = `False` - `destructive: bool` = `False` - `confirm: str | None` = `None` - `separator_after: bool` = `False` ### ui.Command - `id: str` (required) - `label: str` (required) - `icon: str | None` = `None` - `description: str | None` = `None` ### ui.Action A button under an assistant message; ``on_action`` receives its id. - `id: str` (required) - `label: str` (required) - `icon: str | None` = `None` ### ui.Accept What the composer takes: the extensions, how many per message, how large. ``capture`` opens the camera or the microphone straight away on a phone; ``paperclip=False`` hides the attach button while uploads from the chat's ``tools`` still go by these limits. - `accept: list[str] | None` = `None` - `max_files: int` = `5` - `max_size_mb: int | None` = `None` - `capture: 'user' | 'environment' | None` = `None` - `paperclip: bool` = `True` ### ui.FileRef A file the session holds. ``path`` stays on the server. - `id: str` (required) - `name: str` (required) - `size: int` (required) - `mime: str` (required) - `path: Path` (required) - `session: str` (required) - `node: str | None` = `None` ### ui.Issue - `pointer: str` (required) - `message: str` (required) - `code: str | None` = `None` ## The session `s` in a page or handler, or `mw.session()` from code that was not handed it. `s.state` is the session's instance of the model the app declares, kept across pages. `s.user` is the signed-in user or `None`, `s.path` the page's path, `s.params` the URL's parameters, `s.files` the session's files, `s.tasks` the list of its tasks. - `s.text(key, **vars)`: A framework message in the theme's words, for toasts and errors. - `s.toast(message, tone='default', title=None, action=None)`: A toast; ``action=(label, handler)`` adds a button that runs the handler once. - `s.navigate(path)`: Go to another page on this session: its connect runs, the tab's history updates. This returns at once; ``navigate_to`` is the same, awaited. - `s.navigate_to(path)`: Switch this session to another page. The state stays; the page left is closed before the next one connects. - `s.clipboard(text)`: Put text on the tab's clipboard. - `s.download(filename, content, mime=None)`: Send a file to the tab without a button: the tab fetches it over the socket. - `s.logout()`: Clear the sign-in cookie and send the tab to the login page. - `s.reset(key)`: Put the component with this key back to its default: the draft an input holds, a form's fields, a tab strip's choice. - `s.refresh()`: Render soon. Coalesced: many calls in one tick are one patch. - `s.task(work, *args, label=None, key=None, replace=False, delay=None, timeout=None, on_done=None, quiet=False, **kwargs)`: Start work the session owns. - `s.thread(fn, *args, **kwargs)`: ``task`` for a sync function: it runs in a worker thread. - `s.later(delay, work, *args, **kwargs)`: ``task``, started after ``delay`` seconds. - `s.spawn(coro, what='work')`: Run a coroutine for this session: tracked, cancelled on close, failures logged. - `s.ask(component, timeout=None)`: Render a component, wait for its submit or close, return the answer or None. - `s.confirm(title, description=None, ok='OK', cancel='Cancel', destructive=False)`: Ask a yes-or-no question in a dialog and wait for the answer. - `s.chat(key)`: The messages of the Chat with this key; created on first use. - `s.drop_chat(key)`: Forget the thread under this key, unless a reply is running in it: the Chat with that key starts empty next time. An app whose threads live in a store keeps only the open one in the session this way. - `s.end(message)`: Close the session and tell its tab why, so the tab stops instead of reconnecting: signed out elsewhere, or made room for a newer session. ## Tasks `s.task(work)` returns a `mw.Task` and runs `work`, a coroutine or a sync function in a thread. Its `label`, `status` (`pending`, `running`, `done`, `failed`, `cancelled`), `progress`, `steps` and `error` show in `ui.Progress(task=)` and `ui.Steps(task=)`. `await task` gives the result. `on_done` is called as a handler with the task, not the result. - `task.done`: Finished, one way or another: done, failed or cancelled. - `task.ok`: Finished with a result. - `task.cancelled`: Whether ``cancel()`` was called; the work sees it through ``progress().check()``. - `task.result`: What the work returned; raises unless ``ok``. - `task.cancel()`: Stop the work; what it already wrote stays. Inside the work, `mw.progress()` is the task's reporter, and does nothing outside a task. `raise mw.Cancelled` or `p.check()` ends a cancelled task. - `p.set(value=None, message=None)`: How far along, 0 to 1, and the line shown under the bar. - `p.plan(labels)`: Declare steps ahead; ``step(label)`` then opens the one with that label. - `p.step(label)`: ``with p.step("Fetch") as step:`` runs the step; it is done when the block ends, failed when it raises, and ``step.note()`` adds a line. - `p.check()`: Raise ``Cancelled`` after ``task.cancel()``; sync work calls this at sensible points. - `step.note(text)`: A line under the step, replacing the last one. ## Forms `ui.Form(Model, on_submit=)` builds the fields from a Pydantic model and hands the handler a validated instance. The handler rejects it with `raise ui.FormIssues([('/field', 'message')])`, which the form shows at the field. ## The app `mw.App(name, state=None, public='public', origins=None, session_grace=300.0, debug=False, bundle_dir=None, secret=None, files=None, components='components', auth=None, theme=None, csp="default-src 'self'; script-src {scripts}; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; media-src 'self' blob: https:; font-src 'self' data:; connect-src 'self'; frame-src 'self' blob: https:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'", threads=None, keep_alive=False, max_sessions=1000, max_sessions_per_client=20, proxied=False)` A page's path takes parameters: `/items/{id}` is a string, `{id:int}`, `{id:float}`, `{id:uuid}` convert, `{rest:path}` takes the remainder. They reach the page's `connect(session, **params)` and `s.params`. A connect hook raises `mw.NotFound(message)` or `mw.Forbidden(message)` to land the session on the app's not-found or forbidden page. - `app.page(path, title=None, public=False)` - `app.not_found(fn=None, title=None, public=False)`: Decorator: the page shown when no page matches, or connect raises ``NotFound``. - `app.forbidden(fn=None, title=None, public=False)`: Decorator: the page shown when connect raises ``Forbidden``; see ``not_found``. - `app.route(path, endpoint, methods=None)`: A plain Starlette route next to the pages, for an API or a webhook. - `app.mount(path, asgi_app)`: Serve another ASGI app under a path. - `app.middleware(cls, **options)`: Add a Starlette middleware class around the whole app. - `app.on_startup(fn)`: Decorator: runs once when the server starts, before the first session. - `app.on_shutdown(fn)`: Decorator: runs once when the server stops. - `page.connect(fn)`: Decorator: the page's connect hook, ``(session, **params)``. - `page.close(fn)`: Decorator: runs with the session when it leaves the page, or ends on it. ## Testing `from millwork.testing import TestApp`. `async with TestApp(app).open(path, user=) as page` opens a page as a session with no socket; what the app's code raises is raised in the test, or with `strict=False` collected in `page.failures`. `page.find(key or type)` gives a node with `.props`, and `.rows` on a table. `page.state`, `page.toasts`, `page.commands` and `page.stream` are what the tab would have seen. A handler that calls `s.confirm` or `s.ask` waits for `page.answer(value)`. - `page.find(what)`: The first node of a component type, or with a key; raises when there is none. - `page.find_all(what)`: Every node of a component type, or with a key (or a full id). - `page.text()`: Every string prop of the tree, for a quick "is it there" check. A prop that picks from a fixed set, a variant or an alignment, is not text and is left out. - `page.click(node)`: Click it. A ``Download``'s button starts the download, so that is what clicking one does here. - `page.change(node, value)`: An input's new value, as the tab would send it. - `page.submit(form, draft)`: Submit a draft (a dict or a model instance) as the tab would. - `page.select(table, keys)`: Select rows of a table by key. - `page.action(node, action, key=None)`: A row action on a Table (``[action, key]``) or a card action (``action``). - `page.upload(node, path, field=None)`: Put a file into the session for a node, as the upload route would. - `page.send(chat, text, files=(), command=None)`: A chat turn: the message event with text, attachments and a command. - `page.stop(chat)`: Press stop on a chat's running reply. - `page.answer(payload=None)`: Answer the open ``ask``: ``True``/``False`` press OK or Cancel, a dict or model submits the form inside, ``None`` closes the dialog. - `page.toast_action(label)`: Press the button of the latest toast that has one with this label. - `page.navigate(path)`: Take the session to another page, and wait for it to connect and render. - `page.event(node, name, payload=None)`: One event through the real dispatcher, then everything it set off. - `page.settle(timeout=10.0)`: Wait until the session has nothing left to do, or is waiting on the user. ## The theme `public/theme.json`, or `App(theme=)`. - `mode: 'light' | 'dark' | 'system'` = `'system'` - `fonts: list[Font]` = `[]` - `tokens: dict['light' | 'dark', dict[str, str]]` = `{}` - `logo: Logo | None` = `None` - `favicon: str | None` = `None` - `login_image: str | None` = `None` - `scrollbars: 'auto' | 'thin'` = `'thin'` - `density: 'comfortable' | 'compact'` = `'comfortable'` - `locale: str` = `'en'` - `currency: str` = `'EUR'` - `strings: dict[str, str]` = `{}` ## Form widgets `ui.Form(overrides={'/field': {'widget': ...}})` picks one of: `input`, `textarea`, `password`, `number`, `slider`, `switch`, `checkbox`, `select`, `radio`, `toggle`, `multiselect`, `checkboxes`, `tags`, `date`, `datetime`, `file`, `files`, `hidden` ## Formats `ui.Column(format=)` and a chart's `format=`: `text`, `number`, `integer`, `percent`, `currency`, `date`, `datetime`, `bool`, `badge`, `link`, `code` ## Icons The `icon` of a button, link, tab, badge, stat, alert, menu item or option, by lucide's name. Any other name is refused. alert-circle, alert-triangle, archive, arrow-down, arrow-left, arrow-right, arrow-up, arrow-up-down, bar-chart-3, bell, bookmark, bot, brain, calendar, check, check-circle-2, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-up-down, circle, circle-help, clipboard, clock, cloud, code, copy, database, download, edit, external-link, eye, eye-off, file, file-json, file-spreadsheet, file-text, filter, folder, folder-open, grip-vertical, hash, history, home, image, inbox, info, key, languages, layers, link, list, loader-2, lock, log-out, mail, maximize-2, menu, message-square, mic, minimize-2, minus, moon, more-horizontal, more-vertical, paperclip, pause, pencil, play, plus, refresh-cw, save, search, send, settings, share, sparkles, square, square-pen, star, stop-circle, sun, table, tag, terminal, thumbs-down, thumbs-up, trash-2, unlock, upload, user, users, volume-2, wand-2, x, x-circle, zap