novaui 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- novaui-0.1.0/.editorconfig +15 -0
- novaui-0.1.0/.gitattributes +5 -0
- novaui-0.1.0/.gitignore +18 -0
- novaui-0.1.0/ARCHITECTURE.md +102 -0
- novaui-0.1.0/CHANGELOG.md +15 -0
- novaui-0.1.0/CLI.md +34 -0
- novaui-0.1.0/CODE_OF_CONDUCT.md +16 -0
- novaui-0.1.0/COMPONENTS.md +170 -0
- novaui-0.1.0/CONTRIBUTING.md +43 -0
- novaui-0.1.0/EXTENSIONS.md +119 -0
- novaui-0.1.0/FORMS.md +28 -0
- novaui-0.1.0/LICENSE +17 -0
- novaui-0.1.0/PKG-INFO +177 -0
- novaui-0.1.0/README.md +145 -0
- novaui-0.1.0/RELEASING.md +48 -0
- novaui-0.1.0/ROADMAP.md +172 -0
- novaui-0.1.0/ROUTING.md +22 -0
- novaui-0.1.0/SECURITY.md +38 -0
- novaui-0.1.0/benchmarks/phase11.py +106 -0
- novaui-0.1.0/benchmarks/runtime.py +84 -0
- novaui-0.1.0/docs/advanced/extensions.md +21 -0
- novaui-0.1.0/docs/contributing/development.md +15 -0
- novaui-0.1.0/docs/contributing/releases.md +23 -0
- novaui-0.1.0/docs/desktop/native-apis.md +20 -0
- novaui-0.1.0/docs/desktop/runtime.md +24 -0
- novaui-0.1.0/docs/fundamentals/forms-routing.md +40 -0
- novaui-0.1.0/docs/fundamentals/state-events.md +27 -0
- novaui-0.1.0/docs/getting-started/first-app.md +39 -0
- novaui-0.1.0/docs/getting-started/installation.md +46 -0
- novaui-0.1.0/docs/getting-started/project-structure.md +31 -0
- novaui-0.1.0/docs/getting-started/running-apps.md +28 -0
- novaui-0.1.0/docs/index.md +27 -0
- novaui-0.1.0/docs/phase11-applications.md +40 -0
- novaui-0.1.0/docs/reference/cli.md +14 -0
- novaui-0.1.0/docs/reference/components.md +21 -0
- novaui-0.1.0/docs/reference/configuration.md +17 -0
- novaui-0.1.0/docs/reference/troubleshooting.md +20 -0
- novaui-0.1.0/docs/tutorials.md +89 -0
- novaui-0.1.0/examples/__init__.py +1 -0
- novaui-0.1.0/examples/counter.py +26 -0
- novaui-0.1.0/examples/dashboard.py +55 -0
- novaui-0.1.0/examples/data_entry.py +19 -0
- novaui-0.1.0/examples/desktop_clipboard.py +23 -0
- novaui-0.1.0/examples/desktop_menu.py +14 -0
- novaui-0.1.0/examples/desktop_native.py +24 -0
- novaui-0.1.0/examples/desktop_shortcuts.py +17 -0
- novaui-0.1.0/examples/desktop_window.py +18 -0
- novaui-0.1.0/examples/employee_table.py +43 -0
- novaui-0.1.0/examples/extensions_component_pack.py +33 -0
- novaui-0.1.0/examples/extensions_service_plugin.py +34 -0
- novaui-0.1.0/examples/extensions_theme_pack.py +18 -0
- novaui-0.1.0/examples/form_validation.py +28 -0
- novaui-0.1.0/examples/landing.py +38 -0
- novaui-0.1.0/examples/login.py +26 -0
- novaui-0.1.0/examples/phase11_admin/README.md +15 -0
- novaui-0.1.0/examples/phase11_admin/__init__.py +1 -0
- novaui-0.1.0/examples/phase11_admin/app.py +211 -0
- novaui-0.1.0/examples/phase11_design.py +9 -0
- novaui-0.1.0/examples/phase11_pyoffice/README.md +18 -0
- novaui-0.1.0/examples/phase11_pyoffice/__init__.py +1 -0
- novaui-0.1.0/examples/phase11_pyoffice/app.py +133 -0
- novaui-0.1.0/examples/phase11_studio/README.md +17 -0
- novaui-0.1.0/examples/phase11_studio/__init__.py +1 -0
- novaui-0.1.0/examples/phase11_studio/app.py +219 -0
- novaui-0.1.0/examples/reactive_input.py +18 -0
- novaui-0.1.0/examples/registration.py +23 -0
- novaui-0.1.0/examples/reusable.py +25 -0
- novaui-0.1.0/examples/routed_dashboard.py +37 -0
- novaui-0.1.0/examples/settings_app.py +25 -0
- novaui-0.1.0/examples/shared_app.py +11 -0
- novaui-0.1.0/examples/themes.py +21 -0
- novaui-0.1.0/novaui/__init__.py +97 -0
- novaui-0.1.0/novaui/__main__.py +3 -0
- novaui-0.1.0/novaui/app.py +413 -0
- novaui-0.1.0/novaui/cli.py +252 -0
- novaui-0.1.0/novaui/clipboard.py +41 -0
- novaui-0.1.0/novaui/components.py +614 -0
- novaui-0.1.0/novaui/data_table.py +344 -0
- novaui-0.1.0/novaui/desktop.py +204 -0
- novaui-0.1.0/novaui/dialogs.py +22 -0
- novaui-0.1.0/novaui/extensions.py +154 -0
- novaui-0.1.0/novaui/feedback.py +130 -0
- novaui-0.1.0/novaui/forms.py +291 -0
- novaui-0.1.0/novaui/frontend/app.js +689 -0
- novaui-0.1.0/novaui/frontend/index.html +14 -0
- novaui-0.1.0/novaui/frontend/styles.css +216 -0
- novaui-0.1.0/novaui/project.py +23 -0
- novaui-0.1.0/novaui/routing.py +160 -0
- novaui-0.1.0/novaui/server.py +270 -0
- novaui-0.1.0/novaui/shell.py +133 -0
- novaui-0.1.0/novaui/shortcuts.py +101 -0
- novaui-0.1.0/novaui/state.py +90 -0
- novaui-0.1.0/novaui/theme.py +82 -0
- novaui-0.1.0/novaui/window.py +39 -0
- novaui-0.1.0/packaging/novaui_desktop.spec +38 -0
- novaui-0.1.0/pyproject.toml +58 -0
- novaui-0.1.0/tests/test_cli.py +143 -0
- novaui-0.1.0/tests/test_components.py +388 -0
- novaui-0.1.0/tests/test_data_table.py +145 -0
- novaui-0.1.0/tests/test_desktop.py +307 -0
- novaui-0.1.0/tests/test_docs.py +53 -0
- novaui-0.1.0/tests/test_extensions.py +177 -0
- novaui-0.1.0/tests/test_feedback.py +66 -0
- novaui-0.1.0/tests/test_forms.py +75 -0
- novaui-0.1.0/tests/test_packaging.py +20 -0
- novaui-0.1.0/tests/test_phase11_apps.py +199 -0
- novaui-0.1.0/tests/test_routing.py +66 -0
- novaui-0.1.0/tests/test_server.py +311 -0
- novaui-0.1.0/tests/test_shortcuts.py +23 -0
- novaui-0.1.0/tests/test_state.py +57 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
indent_style = space
|
|
8
|
+
indent_size = 4
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
|
|
11
|
+
[*.{yml,yaml,json,toml,md,css,js}]
|
|
12
|
+
indent_size = 2
|
|
13
|
+
|
|
14
|
+
[Makefile]
|
|
15
|
+
indent_style = tab
|
novaui-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# NovaUI Architecture
|
|
2
|
+
|
|
3
|
+
## Direction
|
|
4
|
+
|
|
5
|
+
NovaUI uses a Python declarative API and a browser renderer. The same HTML/CSS/JavaScript frontend can run in a browser or inside a desktop webview. This is not a native-widget renderer.
|
|
6
|
+
|
|
7
|
+
## Layers
|
|
8
|
+
|
|
9
|
+
1. **Public API** (`novaui/__init__.py`): ergonomic exports.
|
|
10
|
+
2. **Components** (`novaui/components.py`): component definitions, validated layout/style properties, and JSON-safe serialization.
|
|
11
|
+
3. **State** (`novaui/state.py`): mutable state objects with subscriptions and dependency collection for computed `Text` values.
|
|
12
|
+
4. **Runtime** (`novaui/app.py`, `novaui/server.py`): lifecycle, HTTP endpoints, event registry.
|
|
13
|
+
5. **Frontend** (`novaui/frontend/`): DOM renderer, CSS tokens, event bridge.
|
|
14
|
+
6. **Desktop host** (`novaui/app.py`, `novaui/desktop.py`): optional `pywebview` adapter over the same local HTTP frontend, with validated window options and a per-window facade.
|
|
15
|
+
7. **Application logic** (`novaui/forms.py`, `novaui/routing.py`, `novaui/shell.py`, `novaui/feedback.py`): Python-side form state/validation, route matching, layout compositions, and event-backed feedback controls.
|
|
16
|
+
8. **Native adapters** (`novaui/dialogs.py`, `novaui/clipboard.py`, `novaui/window.py`, `novaui/shortcuts.py`): explicit desktop-only OS access and per-application shortcut registrations.
|
|
17
|
+
9. **Developer tooling** (`novaui/cli.py`, `novaui/project.py`): argparse commands, starter templates, project options, and a context-local project root for runtime asset resolution.
|
|
18
|
+
10. **Data components** (`novaui/data_table.py`): validated column definitions and server-owned table interaction state.
|
|
19
|
+
|
|
20
|
+
## Component and layout pipeline
|
|
21
|
+
|
|
22
|
+
Component instances serialize to a small JSON tree. The browser renderer (`novaui/frontend/app.js`) maps the known component types to DOM elements and updates existing elements by stable ID. `Row` and `Column` use Flexbox, `Grid` uses CSS Grid with responsive integer-column presets, `Wrap` uses wrapping Flexbox, `ScrollView` uses overflow scrolling, and `Stack` overlays children in one grid cell. `Container`, `Card`, and `Center` provide box, surface, and centering behavior.
|
|
23
|
+
|
|
24
|
+
Shared visual properties serialize as style tokens and are applied with DOM `style` APIs. The Python layer validates class names, common CSS lengths, and a constrained style-property allowlist; it rejects CSS URL and expression values. `Theme` serializes its validated mode, primary color, radius, font family, and optional semantic palette overrides into an allowlist of CSS variables. Omitted palette values preserve the built-in light/dark defaults. The server never evaluates browser-supplied code.
|
|
25
|
+
|
|
26
|
+
## Event flow
|
|
27
|
+
|
|
28
|
+
1. Python builds a component tree and registers handlers with random 256-bit event IDs.
|
|
29
|
+
2. The browser requests `/api/tree` with the private `X-NovaUI-Request: tree` fetch header; this blocks simple cross-site requests from embedding the endpoint as an image or form target.
|
|
30
|
+
3. The frontend renders safe, known component types using DOM APIs.
|
|
31
|
+
4. A user event posts an event ID and, for a bound input, a validated string value to `/api/event`.
|
|
32
|
+
5. Python invokes only the handler registered for that ID; each ID is single-use.
|
|
33
|
+
6. The server returns the serialized tree. Stable component IDs let the frontend reuse unchanged DOM nodes and update changed properties in place.
|
|
34
|
+
|
|
35
|
+
The browser never sends source code or callable names.
|
|
36
|
+
|
|
37
|
+
## Desktop lifecycle and native features
|
|
38
|
+
|
|
39
|
+
`App.run(desktop=True)` lazily imports optional pywebview, binds the existing server to `127.0.0.1` on port `0` by default, starts the server thread, then creates a webview window pointed at the selected port. The GUI loop runs on the main thread. On close or startup failure, the HTTP server is shut down, its socket is closed, its thread is joined, and the component tree is disposed. Desktop mode rejects non-loopback hosts.
|
|
40
|
+
|
|
41
|
+
`WindowOptions` validates sizes, minimum sizes, booleans, and a `#RRGGBB` background. `DesktopWindow` wraps a specific pywebview window instance; it does not rely on a process-global active window. The active desktop window is carried through a `ContextVar` only while registered application events execute. Native file dialogs use pywebview. The text clipboard uses Python's optional-at-build-time Tkinter module and reports unavailable Tk/clipboard environments. Menu items are converted to pywebview native menu objects; GTK does not support those menu objects. Shortcut definitions are application-owned and serialized to the existing frontend as opaque event IDs; they do not expose Python names or source code.
|
|
42
|
+
|
|
43
|
+
The desktop runtime does not expose a general JavaScript-to-Python bridge. The renderer posts registered opaque event IDs through the local HTTP endpoint. The server requires loopback Host values and same-origin `application/json` event requests, bounds concurrent workers and request reads, and limits served project assets to 32 MiB. Responses include a restrictive Content Security Policy, frame denial, and no-referrer policy. Loopback and origin checks reduce browser-based request risk, but do not authenticate local processes or make this a production server.
|
|
44
|
+
|
|
45
|
+
## CLI and project assets
|
|
46
|
+
|
|
47
|
+
The CLI runs the same Python entry point through `runpy`; it does not implement a second app runtime. `novaui run` applies project settings from `[tool.novaui]` (CLI flags override config, which overrides defaults) and enforces loopback binding. The CLI carries the entry point's parent as a `ContextVar` while the app is constructed. `App` captures this asset root; direct script launches infer it from `sys.argv[0]`, while interactive launches use the current working directory.
|
|
48
|
+
|
|
49
|
+
`Image` serializes a relative asset URL under `/assets/`. The HTTP handler decodes the request path, resolves it under `<asset_root>/assets`, checks the resolved target remains inside that directory (including symlinks), and serves a guessed MIME type or 404. Files above 32 MiB are rejected. No arbitrary filesystem path is accepted, and NovaUI does not bundle or transform static files.
|
|
50
|
+
|
|
51
|
+
`novaui check` compiles project Python sources, verifies NovaUI imports and basic `[project]` name/version metadata, then runs pytest if a tests directory exists. `novaui build` delegates source/wheel creation to the standard `build` frontend; it is an optional development tool, not a runtime dependency. Hot reload and standalone executable packaging are not part of the CLI.
|
|
52
|
+
|
|
53
|
+
## Data table
|
|
54
|
+
|
|
55
|
+
`DataTable` validates its `ColumnDef` objects and row mappings during serialization. It applies sort, search, per-column filters, selection, and pagination in Python, then sends only the visible page's formatted cell strings and registered opaque event IDs to the renderer. A `State[list[Mapping]]` is re-read whenever a tree is requested. Formatting returns plain scalar text and the frontend sets it with `textContent`; cell formatters cannot inject HTML. Row click and row-action handlers are registered for that tree and are subject to the existing one-shot event lifecycle. Sorting and filtering currently scan the full in-memory dataset; the table has no virtualization and the local Python timings below do not characterize large-dataset browser responsiveness.
|
|
56
|
+
|
|
57
|
+
## Local Python performance baseline
|
|
58
|
+
|
|
59
|
+
Run `env PYTHONPATH=. .venv/bin/python benchmarks/runtime.py` to reproduce the framework-only baseline. On Linux 7.0.0-31-generic x86_64 with CPython 3.14.4, median times across 7 runs were: construct an App with 100 `Text` children, 0.203 ms; serialize that tree, 0.306 ms; notify one state subscriber, 0.000418 ms; dispatch a button event plus serialize the next tree, 0.010 ms; bind and close the HTTP listening socket without serving requests, 0.103 ms. The 3-column DataTable serialization medians were 0.195 ms for 100 rows, 1.163 ms for 1,000 rows, and 6.557 ms for 5,000 rows, with traced peaks of 16.5, 70.0, and 884.5 KiB respectively. General operations use 100 iterations/run; event and socket operations use 20, and DataTable operations use 10. DataTable memory excludes source data; no browser engine, frontend layout/paint, network round trip, full threaded-server shutdown, or desktop startup is included. These are local baselines, not cross-framework claims.
|
|
60
|
+
|
|
61
|
+
## Forms and navigation
|
|
62
|
+
|
|
63
|
+
`Field` owns a string `State`; `Form` binds fields to existing `Input` components and validates submitted values before calling application code. Form event bodies are bounded mappings of safe field names to strings. Validation callback errors and submission errors are returned as generic messages; logs include only the exception type. Form callbacks are synchronous. This is local application logic, not a replacement for validation in trusted services.
|
|
64
|
+
|
|
65
|
+
`Router` resolves exact static paths and single-segment `{parameter}` patterns in Python. The frontend requests `/api/tree?path=...`; `server.py` serves a fixed `index.html` for non-API paths, so route paths cannot expose files. Browser navigation updates the URL with History API and reloads the Python component tree on `popstate`. The current page factory cache is in-memory. `ui.navigate()` is scoped to the callback currently being dispatched.
|
|
66
|
+
|
|
67
|
+
`AppShell`, `Tabs`, `Alert`, `Spinner`, and `Dialog` are composed or rendered through the same known-component renderer. Dialog confirm and cancel have separate registered event IDs.
|
|
68
|
+
|
|
69
|
+
## Extension boundary
|
|
70
|
+
|
|
71
|
+
`novaui/extensions.py` provides explicit extension configuration. Each `App` copies component, theme, and plugin registrations so services and extension names remain app-scoped. `CustomComponent` composes one cached NovaUI component subtree and delegates serialization to the existing known-component renderer; it cannot emit raw markup or script. Named `Theme` instances use the same token serialization as inline themes. Plugins are application-supplied trusted Python objects: setup is run at app construction, runtime hooks run in deterministic order, and start hooks are unwound in reverse order on shutdown or startup failure. Optional version and module checks use the standard library. Entry-point discovery, sandboxing, renderer plugins, and a stable binary/plugin ABI are intentionally absent.
|
|
72
|
+
|
|
73
|
+
`App.set_theme()` changes the theme used by the next serialized tree, allowing callbacks to switch themes through the existing full-tree event response. The Phase 11 Studio and Admin apps exercise this path. Component metadata is not currently machine-readable, so Studio maintains a curated app-level catalogue. The PyOffice validation app demonstrates native file dialogs and bounded plain-text persistence; NovaUI does not yet provide multiline text editing, charts, or editable grids.
|
|
74
|
+
|
|
75
|
+
## Security boundary and limitations
|
|
76
|
+
|
|
77
|
+
The development server accepts only loopback bind hosts (`127.0.0.1` or `localhost`), validates the Host authority, requires the private fetch header for tree requests, and requires a matching loopback Origin plus JSON content type for event POSTs. Requests without Origin are rejected. The server caps event bodies at 16 KiB, bounds active request workers to 32, sets a 10 second socket timeout, and refuses project assets larger than 32 MiB. The frontend uses known component types and DOM text APIs; the server does not evaluate browser-provided code. See [SECURITY.md](SECURITY.md) for the threat model and reporting policy.
|
|
78
|
+
|
|
79
|
+
These checks do not provide authentication, per-user sessions, isolation from other processes running as the same user, or hardened public hosting. Callback and route code is application code and runs synchronously in a request thread. Desktop applications still expose their local endpoint to processes able to reach loopback. The current browser frontend requires inline style attributes through DOM style properties, so the CSP allows inline styles while restricting scripts to same-origin files.
|
|
80
|
+
|
|
81
|
+
## Important MVP limitations
|
|
82
|
+
|
|
83
|
+
- The handler registry is in-memory and intended for one local application session.
|
|
84
|
+
- The development server binds to loopback.
|
|
85
|
+
- The renderer is intentionally small; components are not yet fully accessible or production hardened.
|
|
86
|
+
- Visual components support validated integer pixel `width` and `height`; shared min/max constraints and margins accept CSS lengths. Width and height themselves remain integer pixels in this release.
|
|
87
|
+
- Form fields are strings and currently render text, email, password, number, and date inputs. Checkbox, radio, select, textarea, native date object conversion, async callbacks, and persistent settings are not implemented.
|
|
88
|
+
- Router route factories and event handlers run synchronously in bounded server request threads. There is no authentication, production session isolation, or hardened public hosting mode.
|
|
89
|
+
- Dialogs are basic native HTML dialogs; focus restoration and complete assistive-technology behavior need further review. `Toast` and global imperative `ui.confirm()` do not exist.
|
|
90
|
+
- The server still serializes the full tree after each event. The frontend reuses existing DOM nodes, but the protocol does not yet send narrow property patches.
|
|
91
|
+
- `Text` tracks `State.value` reads, caches computed text until a dependency changes, and releases its subscriptions when its owning `App` is disposed. Updates from background Python code are not pushed to browsers without a subsequent tree request.
|
|
92
|
+
- Inputs support `State[str]`, optional `on_change`, text/email/password/number/date types, disabled, and read-only modes. They commit on browser `change`; values are limited to 8,000 characters. `on_change` can return a text error message, which is safely displayed and associated with the field. `Form` validates on change and submit.
|
|
93
|
+
- Event request bodies are limited to 16 KiB. Event IDs are single-use and expire when the tree is serialized again. The registry contains handlers for the current tree only.
|
|
94
|
+
- Desktop mode requires a platform webview backend in addition to pywebview. Linux users can install the optional Qt extra; host display and system-library issues remain outside NovaUI's runtime.
|
|
95
|
+
- The tested backend API targets pywebview 6.2; legacy dialog constants are used as a fallback for older supported pywebview releases. Windows WebView2/.NET, macOS Cocoa/WebKit, and Linux GTK/WebKit or Qt/WebEngine dependencies remain OS-managed. No Windows or macOS GUI run has been performed from this environment.
|
|
96
|
+
- Native open/save dialogs return a selected path or `None`; NovaUI never opens or writes the selected path automatically. The current pywebview API has no file-dialog title option. Clipboard support is text-only through Tk and depends on a working desktop clipboard service. Native menus are not supported by GTK; context menus, app icons, and notifications are not included.
|
|
97
|
+
- `Mod` shortcuts map to Ctrl on Windows/Linux and Meta/Command on macOS. They are ignored in text-entry elements unless explicitly allowed. Browser/OS shortcut conflicts are possible.
|
|
98
|
+
- PyInstaller configuration is a sample and has not been run. A frozen app still relies on platform webview/system libraries and should be built and tested on its target OS.
|
|
99
|
+
|
|
100
|
+
## Planned evolution
|
|
101
|
+
|
|
102
|
+
Next: broader form controls, robust route lifecycle and async callbacks, narrower property-patch delivery, push updates for background state changes, accessibility standards, hot reload, and verified distribution packaging. See `ROADMAP.md`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-10-24
|
|
4
|
+
|
|
5
|
+
- Made `novaui/__init__.py` the authoritative package version source, declared the existing MIT license for distribution, removed the unverified placeholder author from package metadata, and explicitly included frontend resources in wheels.
|
|
6
|
+
- Switched the HTTP server to `importlib.resources` for frontend asset access and added package-resource smoke tests.
|
|
7
|
+
- Added version-tagged PyPI Trusted Publishing and manually confirmed TestPyPI verification workflows, plus GitHub issue and pull-request templates.
|
|
8
|
+
- Configured package metadata, public GitHub project links, and PyPI Trusted Publishing workflows; PyPI publication remains pending.
|
|
9
|
+
- Added validated optional semantic theme colors for page backgrounds, surfaces, foreground and muted text, borders, and primary hover states; existing themes retain their defaults when these values are omitted.
|
|
10
|
+
- Reworked the README and added a navigable Markdown documentation set, runnable tutorial gallery, public API reference additions, and automatic documentation/example smoke checks.
|
|
11
|
+
- Documented pre-1.0 API stability and semantic versioning, and clarified private security reporting setup and maintainer response expectations.
|
|
12
|
+
- Added an explicit app-scoped extension configuration for compositional custom components, named themes, trusted plugins, and plugin-owned services. Plugins are never discovered or executed automatically.
|
|
13
|
+
- Hardened the loopback HTTP server with Host and same-origin checks for event requests, a bounded request-worker pool, socket timeouts, response security headers, and a project-asset size limit.
|
|
14
|
+
- Added security regression tests, CI checks, security policy, contribution guide, and release checklist.
|
|
15
|
+
- Recorded the current local DataTable Python serialization baseline and remaining MVP limitations.
|
novaui-0.1.0/CLI.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# NovaUI CLI
|
|
2
|
+
|
|
3
|
+
Install NovaUI (editable from this source checkout with `python -m pip install -e '.[dev]'`) to register the `novaui` command. `python -m novaui` is equivalent.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- `novaui --help`, `novaui --version` — show command help or installed package version.
|
|
8
|
+
- `novaui create my_app` — create a starter project. Names must start with a letter and contain letters, digits, `_`, or `-`. Existing paths are never overwritten.
|
|
9
|
+
- `novaui run` — run `app.py` in the current project, or use `[tool.novaui].entry` from `pyproject.toml`.
|
|
10
|
+
- `novaui run path/to/app.py --mode web|desktop` — select hosting explicitly. `web` is the default. Desktop requires `python -m pip install 'novaui[desktop]'` and a working platform webview.
|
|
11
|
+
- `novaui run --host 127.0.0.1 --port 8000 --open-browser` — choose a loopback address and port and open the browser. Port `0` asks the OS for an available port. `--no-browser` is the default.
|
|
12
|
+
- `novaui run --debug` or `novaui --debug run` — include detailed Python diagnostics. Without debug, ordinary configuration/runtime errors are concise.
|
|
13
|
+
- `novaui check` — compile Python files outside virtualenv/cache/build directories, import NovaUI, validate basic project name/version metadata when `pyproject.toml` exists, and run `pytest -q` in-process if a `tests/` directory exists. If pytest is missing, the test check is clearly marked skipped.
|
|
14
|
+
- `novaui build` — run `python -m build --sdist --wheel` into `dist/`. Install the optional build frontend first with `python -m pip install 'novaui[build]'` (or use the `dev` extra).
|
|
15
|
+
|
|
16
|
+
CLI values override `[tool.novaui]`, which overrides defaults:
|
|
17
|
+
|
|
18
|
+
```toml
|
|
19
|
+
[tool.novaui]
|
|
20
|
+
entry = "app.py"
|
|
21
|
+
mode = "web"
|
|
22
|
+
host = "127.0.0.1"
|
|
23
|
+
port = 8000
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Only these four scalar keys are supported. The development CLI rejects non-loopback hosts. Use `novaui create` for a complete starter layout; install NovaUI into that project's environment before installing its `novaui` dependency when working from an unpublished source checkout.
|
|
27
|
+
|
|
28
|
+
## Assets
|
|
29
|
+
|
|
30
|
+
Put files under the project `assets/` directory. `ui.Image("assets/logo.png", alt="Logo")` renders through the local asset endpoint. The endpoint confines requests to that directory, including symlink resolution, and returns 404 for missing or escaping paths. Other files such as CSS, JavaScript, fonts, and icons can be loaded by the application using `/assets/<relative-path>` URLs. NovaUI does not bundle or transform assets. The CLI and direct script launches resolve the asset root from the entry point's directory; interactive launches use the current working directory.
|
|
31
|
+
|
|
32
|
+
## Exit status and limitations
|
|
33
|
+
|
|
34
|
+
Argument/configuration errors return `2`; application/runtime/check failures return a nonzero status; successful commands return `0`. `check` is a deterministic source syntax/import/test check, not a type checker or security audit. Hot reload is not implemented. `build` creates Python distributions; it does not create a desktop executable. For that, see the PyInstaller sample in `packaging/` and build on the target OS.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Contributor Code of Conduct
|
|
2
|
+
|
|
3
|
+
NovaUI contributors and community members are expected to keep project spaces
|
|
4
|
+
welcoming, respectful, and focused on constructive technical collaboration.
|
|
5
|
+
|
|
6
|
+
Examples of unacceptable behavior include harassment, discrimination,
|
|
7
|
+
intimidation, personal attacks, and publishing another person's private
|
|
8
|
+
information without permission.
|
|
9
|
+
|
|
10
|
+
Please report conduct concerns privately to the repository owner through the
|
|
11
|
+
hosting service's private contact mechanism. This repository does not currently
|
|
12
|
+
publish a monitored conduct-reporting address. Maintainers should enable a
|
|
13
|
+
private reporting channel before opening public community spaces.
|
|
14
|
+
|
|
15
|
+
Maintainers may edit, remove, or decline contributions that violate this policy
|
|
16
|
+
and may restrict participation when necessary to protect community members.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# NovaUI Component Reference
|
|
2
|
+
|
|
3
|
+
This reference documents components currently implemented by NovaUI. Components are Python objects that serialize to a validated tree and render through the shared HTML/CSS/JavaScript frontend.
|
|
4
|
+
|
|
5
|
+
## Shared properties
|
|
6
|
+
|
|
7
|
+
All current components accept these keyword-only properties:
|
|
8
|
+
|
|
9
|
+
| Property | Accepted values | Default | Behavior |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| `margin` | pixel integer or CSS length; margin also accepts 1–4 CSS length tokens | `None` | Outer spacing |
|
|
12
|
+
| `min_width`, `max_width`, `min_height`, `max_height` | pixel integer or CSS length | `None` | CSS min/max size |
|
|
13
|
+
| `background`, `border`, `shadow` | safe CSS value string | `None` | Inline visual token |
|
|
14
|
+
| `visible` | `bool` | `True` | Hides the rendered element when false |
|
|
15
|
+
| `class_name` | CSS class names separated by spaces | `None` | Adds classes to the rendered element |
|
|
16
|
+
| `style` | Mapping of allowed CSS property names to strings or numbers | `None` | Applied with DOM style properties; numbers use pixels except unitless CSS properties |
|
|
17
|
+
|
|
18
|
+
Style values reject declaration delimiters, markup delimiters, `url()`, and `expression()`. Supported custom style properties include color, background color, borders, typography, spacing, overflow, and grid columns. Component-specific properties take precedence where they set the same CSS property.
|
|
19
|
+
|
|
20
|
+
The visual components `Text`, `Button`, `Image`, `Input`, `Card`, `Center`, `Container`, `Grid`, `ScrollView`, `Stack`, `Wrap`, and `Spacer` also accept `width` and `height` as non-negative integer pixels.
|
|
21
|
+
|
|
22
|
+
## Content and controls
|
|
23
|
+
|
|
24
|
+
### `Text(text, size=None, bold=False, color=None)`
|
|
25
|
+
|
|
26
|
+
Renders text with `textContent` (never as HTML). `text` may be a string or zero-argument callable. Callables track reads from `State` instances and recalculate when those states change. `size` is a non-negative pixel integer; `bold` is a bool; `color` is a CSS color string.
|
|
27
|
+
|
|
28
|
+
### `Image(src, alt, width=None, height=None)`
|
|
29
|
+
|
|
30
|
+
Displays a local file from the app's `assets/` directory, for example `ui.Image("assets/logo.png", alt="NovaUI logo")`. `alt` is required. Absolute paths, parent-directory traversal, and paths outside `assets/` are rejected. The CLI resolves assets relative to the entry-point directory; direct Python launches use the working directory. Other static files can be requested at `/assets/<path>` without a bundler.
|
|
31
|
+
|
|
32
|
+
### `Button(label, on_click=None, disabled=False, variant="primary")`
|
|
33
|
+
|
|
34
|
+
Renders a keyboard-operable button. `on_click` is registered with an opaque one-shot event ID. `variant` is `primary` or `secondary`.
|
|
35
|
+
|
|
36
|
+
### `Input(placeholder="", value="", password=False, label=None, on_change=None, disabled=False, read_only=False, input_type="text", ...)`
|
|
37
|
+
|
|
38
|
+
Renders a text or password field. `value` may be a string or `State[str]`. When bound or when `on_change` is supplied, the browser sends the changed string through the validated event bridge. `on_change(value)` may return an error string or `None`; errors are rendered as text and associated with the field.
|
|
39
|
+
|
|
40
|
+
`input_type` supports `text`, `email`, `password`, `number`, and `date`; `password=True` remains a shorthand for password type. Optional `name`, `required`, `min_length`, `max_length`, `min_value`, and `max_value` set the corresponding field semantics and are validated in Python. See [Forms](FORMS.md) for Python-side `Field`/`Form` validation.
|
|
41
|
+
|
|
42
|
+
## Layouts
|
|
43
|
+
|
|
44
|
+
### `Animated(child, enter="fade", duration=240, easing="ease-out")`
|
|
45
|
+
|
|
46
|
+
Wraps one component and applies a CSS entrance animation the first time that component node is mounted. Presets are `fade`, `fade_up`, `fade_down`, `slide_in`, `scale_in`, and `pop`; easing is one of `linear`, `ease`, `ease-in`, `ease-out`, or `ease-in-out`; duration is an integer from 0 to 2000 milliseconds. Nested child behavior and events use the normal component renderer. The wrapper remains mounted during state updates, so the entrance effect does not replay on each update. Exit animation is not supported because the current reconciliation lifecycle removes obsolete nodes immediately. The frontend honors `prefers-reduced-motion` for animations, transitions, and the spinner.
|
|
47
|
+
|
|
48
|
+
All layout constructors accept child components as positional arguments. Empty layouts render as empty elements.
|
|
49
|
+
|
|
50
|
+
### `Row(*children, spacing=8, padding=0, align="center", justify="start")`
|
|
51
|
+
|
|
52
|
+
Flexbox horizontal layout. `align` accepts `start`, `center`, `end`, `stretch`, or `baseline`; `justify` accepts `start`, `center`, `end`, `between`, `around`, or `evenly`. Rows wrap on narrow screens.
|
|
53
|
+
|
|
54
|
+
### `Column(*children, spacing=8, padding=0, align="stretch", justify="start")`
|
|
55
|
+
|
|
56
|
+
Flexbox vertical layout, with the same alignment options as `Row`.
|
|
57
|
+
|
|
58
|
+
### `Card(*children, padding=16, radius=12)`
|
|
59
|
+
|
|
60
|
+
Surface section with theme background, border, and shadow defaults. `radius` is a non-negative pixel integer.
|
|
61
|
+
|
|
62
|
+
### `Center(child)`
|
|
63
|
+
|
|
64
|
+
Centers one child using Flexbox.
|
|
65
|
+
|
|
66
|
+
### `Container(*children, padding=0)`
|
|
67
|
+
|
|
68
|
+
Generic box for sizing and shared visual properties.
|
|
69
|
+
|
|
70
|
+
### `Grid(*children, columns=2, spacing=8, padding=0)`
|
|
71
|
+
|
|
72
|
+
CSS Grid layout. `columns` is an integer from 1 to 12 or a CSS grid-template string. Integer templates use equal-width columns and collapse to two columns on tablet widths and one on narrow screens when the configured count is greater than one.
|
|
73
|
+
|
|
74
|
+
### `ScrollView(*children, direction="vertical", padding=0)`
|
|
75
|
+
|
|
76
|
+
Scrollable container. `direction` is `vertical`, `horizontal`, or `both`. Use `height`, `width`, `max_height`, or `max_width` to constrain its viewport.
|
|
77
|
+
|
|
78
|
+
### `Stack(*children)`
|
|
79
|
+
|
|
80
|
+
Overlays children in a shared CSS Grid cell. Give the stack dimensions or constrain it through shared min/max properties when needed.
|
|
81
|
+
|
|
82
|
+
### `Wrap(*children, spacing=8, run_spacing=8, align="start")`
|
|
83
|
+
|
|
84
|
+
Wrapping Flexbox layout. `spacing` sets horizontal gaps; `run_spacing` sets gaps between wrapped lines. `align` is `start`, `center`, `end`, or `stretch`.
|
|
85
|
+
|
|
86
|
+
### `Spacer(size=8)`
|
|
87
|
+
|
|
88
|
+
Flexible empty space in a row or column. `size` is a non-negative pixel minimum.
|
|
89
|
+
|
|
90
|
+
## Data components
|
|
91
|
+
|
|
92
|
+
### `ColumnDef(key, title=None, width=None, sortable=True, formatter=None)`
|
|
93
|
+
|
|
94
|
+
Defines one table column. Keys must be unique valid field names and must exist in every row. `width` accepts non-negative pixel integers or supported CSS lengths. `formatter(value, row)` may return text or a scalar; results are always rendered as text, never HTML.
|
|
95
|
+
|
|
96
|
+
### `DataTable(columns, data, selectable=False, sortable=True, searchable=True, filterable=True, page_size=20, ...)`
|
|
97
|
+
|
|
98
|
+
Renders a semantic, horizontally scrollable table. `data` is a sequence of mappings or a `State` containing one. Header buttons sort ascending/descending; a global search box and per-column filter fields update server-owned filters. Pagination is one-based. Select-all applies to the visible page; `on_selection_change(rows)` receives all currently selected records. Use `row_key` for a unique stable field; without it, selection keys use the source row positions.
|
|
99
|
+
|
|
100
|
+
`on_row_click(row)` handles accessible mouse and Enter/Space activation of a row. `row_actions` accepts `RowAction(label, on_click)` items and calls each callback with the row. `loading=True` disables actions and shows a loading status. `empty_message` controls the empty-state copy.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
ui.DataTable(
|
|
104
|
+
columns=[ui.ColumnDef("name", title="Employee"), ui.ColumnDef("salary")],
|
|
105
|
+
data=[{"id": 1, "name": "Alice", "salary": 65000}],
|
|
106
|
+
row_key="id",
|
|
107
|
+
selectable=True,
|
|
108
|
+
page_size=20,
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The table scans/sorts the in-memory dataset on each tree request but sends only the current page's formatted rows to the browser. It is intended for small and medium datasets; it has no virtualization. Search/filter callbacks update the table after the browser `change` event, and NovaUI currently returns the full application tree after each interaction.
|
|
113
|
+
|
|
114
|
+
Local baseline (Linux 7.0.0-31-generic x86_64, CPython 3.14.4; median across 7 runs, 10 serializations per run, 3 columns sorted descending, page size 20; one additional `tracemalloc` serialization): 100 rows — 0.195 ms / 16.5 KiB; 1,000 rows — 1.163 ms / 70.0 KiB; 5,000 rows — 6.557 ms / 884.5 KiB. Source data is allocated before tracing. Timing excludes browser rendering and HTTP dispatch; memory excludes source data. The reproducible Python-only baseline, including tree construction, state notification, event dispatch, and server socket bind/close, is `env PYTHONPATH=. .venv/bin/python benchmarks/runtime.py`. Figures are environment-specific, not a performance guarantee; full sorting/filtering scans all rows.
|
|
115
|
+
|
|
116
|
+
## Forms, navigation, and feedback
|
|
117
|
+
|
|
118
|
+
- `Field(value="", label=None, input_type="text", required=False, min_length=None, max_length=None, min_value=None, max_value=None, validator=None, disabled=False)` tracks a string value, dirty state, and validation result.
|
|
119
|
+
- `Form(fields=..., on_submit=..., submit_label="Submit", reset_label="Reset", disabled=False, loading=False)` validates submitted string values in Python. It exposes `values`, `errors`, `dirty_fields`, `revalidate()`, `reset()`, and `submit(values)`.
|
|
120
|
+
- `Router(routes=..., not_found=None)` maps paths to components or factories. Route factories accept `{parameter}` path values; `params`, `query_params`, and `current_path` describe the resolved URL. Call `ui.navigate(path)` from a component event callback.
|
|
121
|
+
- `AppShell`, `Header`, `Sidebar`, `MainContent`, `Footer`, `NavigationItem`, and `Breadcrumbs` compose the standard layout components. `Sidebar(collapsed=True)` selects a narrow width; it does not provide interactive collapse state.
|
|
122
|
+
- `Tabs({label: component}, selected=None)` serializes the selected panel and supports a `State[str]` selection.
|
|
123
|
+
- `Alert(message, kind="info")` accepts `info`, `success`, `warning`, or `error`; `Spinner(label="Loading")` displays a status indicator.
|
|
124
|
+
- `Dialog(title, child, open=False, on_confirm=None, on_cancel=None, confirm_label="Confirm", cancel_label="Cancel")` provides separate confirm and cancel events. Pass `State[bool]` for controlled visibility.
|
|
125
|
+
|
|
126
|
+
See [Forms](FORMS.md) and [Routing](ROUTING.md) for runnable examples and security limits. Checkboxes, radio groups, select, textarea, toast queues, and asynchronous form handlers are not implemented.
|
|
127
|
+
|
|
128
|
+
## Shell, theme, and application APIs
|
|
129
|
+
|
|
130
|
+
- `AppShell(*children, **shared_props)` composes its children in the standard shell layout. `Header`, `MainContent`, and `Footer` are layout helpers with the same child-first convention.
|
|
131
|
+
- `Sidebar(*children, collapsed=False, **shared_props)` renders a sidebar; `collapsed` chooses a narrow width and is not an interactive toggle. `NavigationItem(label, href, active=False)` renders a navigation link. `Breadcrumbs(items)` accepts labels or `(label, href)` pairs.
|
|
132
|
+
- `Theme(mode="light", primary="#6558d3", radius="10px", font_family="Inter, ui-sans-serif, system-ui, sans-serif", name=None, background=None, surface=None, foreground=None, muted=None, border=None, primary_hover=None)` validates theme tokens. Optional colors override `--bg`, `--surface`, `--text`, `--muted`, `--border`, and `--primary-hover`; omitted values retain the built-in light/dark palette. `name` is used for explicit extension registration; it is omitted from renderer output. See [extensions](EXTENSIONS.md).
|
|
133
|
+
- `State(value)` and `ui.state(value)` provide mutable Python state. Read and assign `.value`, call `.update(value)`, subscribe with `.subscribe(callback)`, and release listeners with `.dispose()`.
|
|
134
|
+
- `App(root, title="NovaUI", theme="light", host="127.0.0.1", port=0, debug=False, extensions=None)` creates a runtime object. `App.set_theme(theme)` accepts `"light"`, `"dark"`, an app-registered theme name, or a `Theme` instance; call it in an event callback to change tokens on the next tree render. `ui.app(root, title=..., theme=..., debug=False, host="127.0.0.1", port=0, extensions=None)` creates and immediately runs it. `ui.desktop(root, title=..., theme=..., width=1200, height=800, min_width=200, min_height=100, resizable=True, fullscreen=False, frameless=False, background_color="#FFFFFF", debug=False, menu=(), on_close=None, extensions=None)` runs it in the optional webview host.
|
|
135
|
+
- `WindowOptions(width=1200, height=800, min_width=200, min_height=100, resizable=True, fullscreen=False, frameless=False, background_color="#FFFFFF")` carries validated desktop settings. `Menu(title, items)`, `MenuItem(title, callback)`, and `MenuSeparator()` describe supported native menu entries.
|
|
136
|
+
- `ShortcutRegistry.register(shortcut, callback, prevent_default=True, allow_in_inputs=False, replace=False)` accepts combinations such as `Mod+S`; `unregister(shortcut)`, `set_enabled(shortcut, enabled)`, and `clear()` manage registrations. Callbacks take no arguments. `Mod` resolves to Ctrl on Windows/Linux and Meta on macOS; shortcuts are ignored in input controls unless opted in.
|
|
137
|
+
- Extension APIs are `Extensions.register_component(name, CustomComponentSubclass)`, `register_theme(named_theme)`, and `use(plugin_instance)`. `CustomComponent.render()` returns a built-in component. See [the extension guide](EXTENSIONS.md) for plugin hooks, compatibility attributes, and trust limitations.
|
|
138
|
+
|
|
139
|
+
Window controls, file dialogs, clipboard, and shortcuts are runtime integrations rather than visual components. Their support depends on the desktop backend; see [native desktop APIs](docs/desktop/native-apis.md). The root-level `ui.window`, `ui.dialogs`, and `ui.clipboard` modules are only usable from an active desktop event callback.
|
|
140
|
+
|
|
141
|
+
## Themes
|
|
142
|
+
|
|
143
|
+
Pass a `Theme` to `ui.app` to set CSS variables:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
theme = ui.Theme(
|
|
147
|
+
mode="light",
|
|
148
|
+
primary="#635BFF",
|
|
149
|
+
radius="12px",
|
|
150
|
+
font_family="Inter, sans-serif",
|
|
151
|
+
background="#f7f8fc",
|
|
152
|
+
surface="#ffffff",
|
|
153
|
+
foreground="#202333",
|
|
154
|
+
)
|
|
155
|
+
ui.app(ui.Text("Hello"), theme=theme)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`mode` is `light` or `dark`; color fields must be CSS color names, hex, RGB, or HSL values; `radius` is a non-negative pixel integer or CSS length; and `font_family` accepts a restricted family-list string. Existing `theme="light"` and `theme="dark"` calls remain supported.
|
|
159
|
+
|
|
160
|
+
## Examples
|
|
161
|
+
|
|
162
|
+
- `python examples/landing.py` — responsive page layout and custom theme.
|
|
163
|
+
- `python examples/dashboard.py` — sidebar-style layout, cards, grid, and scrolling.
|
|
164
|
+
- `python examples/form_validation.py` — input callback validation.
|
|
165
|
+
- `python examples/login.py` — controlled login fields.
|
|
166
|
+
- `python -m examples.phase11_studio.app` — predefined component playground.
|
|
167
|
+
- `python -m examples.phase11_admin.app` — routed administration dashboard.
|
|
168
|
+
- `python -m examples.phase11_pyoffice.app` — desktop plain-text notes prototype (desktop extra required).
|
|
169
|
+
|
|
170
|
+
Icons, dividers, badges, tooltips, checkbox/radio/select/textarea controls, toast notifications, and machine-readable component metadata remain planned.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
NovaUI is an early-stage project. Public APIs may evolve before a stable 1.0 release. Avoid breaking documented behavior without tests, migration notes, and a roadmap update.
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python -m venv .venv
|
|
9
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
10
|
+
python -m pip install -e '.[dev]'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run checks from the repository root:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m ruff check novaui tests examples packaging
|
|
17
|
+
python -m pytest -q
|
|
18
|
+
node --check novaui/frontend/app.js
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
HTTP integration tests bind to loopback. Environments that prohibit local sockets cannot run those tests successfully.
|
|
22
|
+
|
|
23
|
+
## Change expectations
|
|
24
|
+
|
|
25
|
+
- Add tests for behavior and security-sensitive boundaries.
|
|
26
|
+
- Keep browser payloads structured and never execute client-provided code.
|
|
27
|
+
- Avoid logging application input or secrets.
|
|
28
|
+
- Document implemented APIs and limitations; do not mark planned work complete.
|
|
29
|
+
- Keep base dependencies minimal and optional integrations in extras.
|
|
30
|
+
- Include browser/desktop manual-test notes when a change needs a GUI.
|
|
31
|
+
- New components should include construction/serialization tests, a component-reference entry, and a runnable example where practical.
|
|
32
|
+
|
|
33
|
+
## Issues and pull requests
|
|
34
|
+
|
|
35
|
+
Bug reports should include the NovaUI version or commit, Python/OS details, a minimal reproducible example, expected behavior, and actual behavior. Remove passwords, tokens, personal data, and private files before sharing logs. Feature proposals should describe the user problem and a small API sketch; discuss larger changes before implementing them. Pull requests should state the behavior changed, tests and documentation updated, commands run, and any manual platform checks. Keep each change focused and do not claim untested platform support.
|
|
36
|
+
|
|
37
|
+
## API stability
|
|
38
|
+
|
|
39
|
+
NovaUI follows semantic versioning for released versions: patch releases are for compatible fixes, minor releases add backward-compatible features, and major releases may break APIs. Before 1.0, the public API is experimental unless a specific interface is marked stable in its docs; pre-1.0 minor releases may still revise experimental APIs. We aim to preserve documented APIs; incompatible changes require a reason, migration guidance, updated examples, and tests. Deprecations should be documented for at least one release before removal where practical.
|
|
40
|
+
|
|
41
|
+
## Security issues
|
|
42
|
+
|
|
43
|
+
See [SECURITY.md](SECURITY.md). Do not publish exploit details in an issue before contacting the maintainer privately.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# NovaUI Extensions
|
|
2
|
+
|
|
3
|
+
NovaUI 0.1 exposes a small, explicit extension API for Python code trusted by
|
|
4
|
+
the application owner. Extensions run in the application process with the same
|
|
5
|
+
permissions as the application. They are **not sandboxed**. NovaUI does not
|
|
6
|
+
automatically discover or execute installed plugins.
|
|
7
|
+
|
|
8
|
+
## Per-application registration
|
|
9
|
+
|
|
10
|
+
Create an `Extensions` configuration, register additions, then pass it to
|
|
11
|
+
`ui.App`, `ui.app`, or `ui.desktop`. An `App` copies the registrations so
|
|
12
|
+
separate applications do not share service/component/theme registries.
|
|
13
|
+
Plugin objects themselves are the instances supplied by the caller; create a
|
|
14
|
+
fresh `Extensions` object and plugin instance for each independently configured
|
|
15
|
+
application if the plugin stores mutable per-app state.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import novaui as ui
|
|
19
|
+
|
|
20
|
+
class StatusBadge(ui.CustomComponent):
|
|
21
|
+
def __init__(self, text: str, status: str = "ok"):
|
|
22
|
+
super().__init__()
|
|
23
|
+
if status not in {"ok", "error"}:
|
|
24
|
+
raise ValueError("status must be 'ok' or 'error'")
|
|
25
|
+
self.text, self.status = text, status
|
|
26
|
+
|
|
27
|
+
def render(self) -> ui.Component:
|
|
28
|
+
color = "#087443" if self.status == "ok" else "#b42318"
|
|
29
|
+
return ui.Text(self.text, color=color, bold=True)
|
|
30
|
+
|
|
31
|
+
extensions = ui.Extensions()
|
|
32
|
+
extensions.register_component("StatusBadge", StatusBadge)
|
|
33
|
+
ui.app(ui.Column(StatusBadge("Connected")), extensions=extensions)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Custom components subclass `CustomComponent` and implement `render()` to
|
|
37
|
+
return one NovaUI `Component`. NovaUI caches that returned subtree per instance
|
|
38
|
+
and sends it through the ordinary renderer and event registry. Common styling
|
|
39
|
+
on a custom instance uses the existing `Container` renderer, which adds a
|
|
40
|
+
wrapper only when styling is set. Custom components cannot return raw HTML,
|
|
41
|
+
JavaScript, renderer instructions, or arbitrary JSON. Names must start with a
|
|
42
|
+
letter and contain up to 64 ASCII letters, digits, or underscores. Duplicate
|
|
43
|
+
names in one registry are rejected. A custom component must be registered in
|
|
44
|
+
each `Extensions` configuration used by an app.
|
|
45
|
+
|
|
46
|
+
## Themes
|
|
47
|
+
|
|
48
|
+
`Theme` supports validated `mode`, `primary`, `radius`, and `font_family`
|
|
49
|
+
tokens. A named theme can be registered and selected by name:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
extensions = ui.Extensions()
|
|
53
|
+
extensions.register_theme(ui.Theme(
|
|
54
|
+
name="corporate", mode="light", primary="#3157D5", radius="10px",
|
|
55
|
+
))
|
|
56
|
+
ui.app(ui.Text("Hello"), theme="corporate", extensions=extensions)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Theme registration rejects unnamed themes and duplicate names. The selected
|
|
60
|
+
theme is serialized using the same renderer tokens as an inline `Theme`; it
|
|
61
|
+
does not merge with another theme. Application selection wins because exactly
|
|
62
|
+
one theme is sent to the frontend.
|
|
63
|
+
|
|
64
|
+
## Plugins and services
|
|
65
|
+
|
|
66
|
+
Plugins are explicit trusted Python instances. Their unique `name` is required.
|
|
67
|
+
The optional `setup(app)` runs once while the `App` is constructed. Plugins may
|
|
68
|
+
register app-scoped services through `app.register_service(name, service)`;
|
|
69
|
+
application callbacks can retrieve them using `app.get_service(name)`. Duplicate
|
|
70
|
+
service names are errors.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
class MetricsPlugin:
|
|
74
|
+
name = "metrics"
|
|
75
|
+
min_novaui_version = "0.1.0" # inclusive
|
|
76
|
+
max_novaui_version_exclusive = "1.0.0"
|
|
77
|
+
optional_dependencies = ("my_metrics_package",)
|
|
78
|
+
|
|
79
|
+
def setup(self, app: ui.App) -> None:
|
|
80
|
+
app.register_service("metrics", MetricsService())
|
|
81
|
+
|
|
82
|
+
def on_app_start(self, app: ui.App) -> None:
|
|
83
|
+
app.get_service("metrics").start()
|
|
84
|
+
|
|
85
|
+
def on_app_stop(self, app: ui.App) -> None:
|
|
86
|
+
app.get_service("metrics").stop()
|
|
87
|
+
|
|
88
|
+
extensions = ui.Extensions()
|
|
89
|
+
extensions.use(MetricsPlugin())
|
|
90
|
+
application = ui.App(ui.Text("Metrics enabled"), extensions=extensions)
|
|
91
|
+
application.run()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Setup runs in registration order. Version checks use `major.minor.patch`, with
|
|
95
|
+
an inclusive minimum and exclusive maximum. Missing optional modules and
|
|
96
|
+
incompatible NovaUI versions fail app construction with an actionable error.
|
|
97
|
+
`on_app_start(app)` runs in registration order immediately before runtime
|
|
98
|
+
startup; `on_app_stop(app)` runs in reverse order for every plugin whose start
|
|
99
|
+
hook began, including when a later startup hook fails. A startup failure stops
|
|
100
|
+
the application instead of continuing with partial functionality. Shutdown
|
|
101
|
+
hook failures are logged by exception type and do not prevent other plugins or
|
|
102
|
+
app resources from being cleaned up. Hooks are synchronous.
|
|
103
|
+
|
|
104
|
+
Plugins should keep `setup` limited to registration/configuration and allocate
|
|
105
|
+
runtime resources in `on_app_start`, releasing them in `on_app_stop`. NovaUI
|
|
106
|
+
does not provide process isolation, permission prompts, dependency solving, or
|
|
107
|
+
automatic package entry-point discovery. Importing a plugin module executes
|
|
108
|
+
Python code; only import plugins that you trust.
|
|
109
|
+
|
|
110
|
+
## Public API and limitations
|
|
111
|
+
|
|
112
|
+
The supported extension surface is `Extensions`, `CustomComponent`, named
|
|
113
|
+
`Theme`, `App.register_component`, `App.register_theme`,
|
|
114
|
+
`App.register_service`, `App.get_service`, and the three plugin hooks above.
|
|
115
|
+
The component registry, renderer serialization format, and frontend protocol
|
|
116
|
+
remain internal and may change. There is no extension marketplace, hot reload,
|
|
117
|
+
remote plugin support, sandbox, or stable plugin ABI guarantee in NovaUI 0.1.
|
|
118
|
+
For a sample of each extension type, run `examples/extensions_component_pack.py`,
|
|
119
|
+
`examples/extensions_theme_pack.py`, or `examples/extensions_service_plugin.py`.
|
novaui-0.1.0/FORMS.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
`ui.Field` keeps a string value and its validation rules; `ui.Form` renders fields, collects browser input through generated event IDs, validates on the Python side, and invokes a synchronous callback with a copy of the submitted values.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
email = ui.Field(label="Email", input_type="email", required=True)
|
|
7
|
+
password = ui.Field(label="Password", input_type="password", required=True, min_length=8)
|
|
8
|
+
|
|
9
|
+
def sign_in(values: dict[str, str]) -> None:
|
|
10
|
+
# Validate credentials and authorize against trusted application services here.
|
|
11
|
+
print("Sign in requested for", values["email"])
|
|
12
|
+
|
|
13
|
+
form = ui.Form(fields={"email": email, "password": password}, on_submit=sign_in)
|
|
14
|
+
ui.app(form)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Supported field types are `text`, `email`, `password`, `number`, and `date`. Fields support `required`, `min_length`, `max_length`, numeric `min_value`/`max_value`, a `validator(value) -> str | None`, `disabled`, and `placeholder`. `Form` exposes `values`, `errors`, `dirty_fields`, `revalidate()`, `reset()`, `submit_label`, `reset_label`, and external `disabled`/`loading` flags. A successful callback may return a status string; callback exceptions produce a generic status and a safe log entry. Because callbacks are synchronous, the browser cannot show a transient loading indicator while a callback is running. Async callbacks are not supported yet.
|
|
18
|
+
|
|
19
|
+
Custom validators run in Python:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
def validate_handle(value: str) -> str | None:
|
|
23
|
+
return "Use at least one letter." if not any(ch.isalpha() for ch in value) else None
|
|
24
|
+
|
|
25
|
+
handle = ui.Field(required=True, validator=validate_handle)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The browser's native constraints are only a usability aid. Form checks in this local UI process are not a security boundary. Repeat sensitive validation and authorization in trusted server-side business logic. Password values are not stored in browser storage or logged by NovaUI, but the current event bridge sends form values to the Python process over the local HTTP connection.
|
novaui-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NovaUI Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|