2 Commits
Author SHA1 Message Date
John Lancaster 209c48987c separated cli settings 2026-07-31 19:23:12 -05:00
John Lancaster 4ed1f43eda ui instructions 2026-07-31 16:01:23 -05:00
3 changed files with 72 additions and 11 deletions
+49 -2
View File
@@ -1,6 +1,53 @@
--- ---
description: Copilot rules for modifying the UI description: "Use when modifying the NiceGUI application under src/transcription/ui. Defines ownership and dependency boundaries for UI registration, pages, components, services, persistence, state, and static assets."
applyTo: 'src/transcription/ui/**/*.py' applyTo: 'src/transcription/ui/**/*.py'
--- ---
The UI is forbidden from directly touching the database. All interactions should be done using the [services](../../src/transcription/services/) # UI Conceptual Boundaries
Keep dependencies flowing in this direction:
`ui/__init__.py` -> `pages` -> `components`
Pages may depend on application services and framework-provided dependencies. Components may depend on smaller components and shared presentation helpers. Services and domain modules must never depend on the UI.
## Package Root
- Keep `ui/__init__.py` as the UI composition root: register global assets, register pages, and mount NiceGUI on FastAPI.
- Do not put feature rendering, service calls, persistence, or route-specific state in the package root.
## Pages
- Pages own route registration and route-level orchestration.
- Resolve request or application dependencies, call [services](../../src/transcription/services/), adapt returned data for presentation when needed, and coordinate refresh, navigation, and notifications here.
- Do not query, mutate, commit, or roll back the database from a page. Do not import database engines, sessions, operations, or query-building APIs. Persistence belongs to services or workflow functions.
- Framework dependency types may cross into page handlers only to construct or invoke services; do not pass sessions or session factories into components.
- Keep business rules, lifecycle transitions, transaction boundaries, and cross-service workflows out of page callbacks.
## Components
- Components own reusable rendering, widget-local state, input normalization, and presentation-only formatting.
- Expose user actions through typed callback parameters. The calling page decides which service or workflow runs and what refresh or navigation follows.
- Do not register routes, resolve request/app state, instantiate services, or access persistence from components.
- Components may accept ORM models returned by services as read-only snapshots. Only use fields and relationships that the service loaded eagerly; never mutate models, trigger lazy loading, or expose session behavior.
- A component may compose lower-level components, but it must not import from `pages`.
## Shared UI Infrastructure
- Keep app-wide navigation and layout primitives in `components/app_shell.py`.
- Keep generic table/event adaptation in `components/table/common.py`; feature-specific columns, row read models, and formatting belong in the feature table module.
- Keep exception normalization and user-facing error display in `components/error_presenter.py`; preserve `AppError` details and operation identifiers at page/component boundaries.
## CSS Assets
- Keep CSS under `ui/static` and split it into manageable, feature-oriented files. Do not grow a monolithic stylesheet or embed substantial style blocks in Python components.
- Load each stylesheet from the page, component, or composition root that needs it with `ui.add_css(...)`. Use shared registration only for genuinely application-wide styles.
- Read stylesheet text through `importlib.resources.files(...)` so loading works from installed packages and is independent of the working directory.
- Centralize CSS reading in one typed helper cached by relative resource path with `functools.cache` or an equivalent unbounded `lru_cache`. Cache the immutable stylesheet text to avoid repeated resource I/O during component renders; keep NiceGUI registration decisions at the caller.
- Do not encode application behavior in CSS or other static assets.
## State and Side Effects
- Limit component state to ephemeral interaction state such as loading flags, form values, dialogs, and expansion state.
- Application and worker state must be resolved at the page or application boundary and passed through narrow interfaces such as callbacks or notifier protocols.
- Keep filesystem, network, provider, and worker orchestration behind application services or dedicated adapters. UI code may trigger those operations but must not implement them.
+13 -6
View File
@@ -1,17 +1,24 @@
import uvicorn import uvicorn
from fastapi import FastAPI
from .config import LOGGING_CONFIG from .app import create_app
from .config import get_settings from .config import parse_cli_settings
def create_cli_app() -> FastAPI:
"""Create an app from CLI settings for Uvicorn's reload process."""
return create_app(settings=parse_cli_settings())
def main() -> None: def main() -> None:
settings = get_settings() settings = parse_cli_settings()
application = "transcription.__main__:create_cli_app" if settings.reload else create_app(settings=settings)
uvicorn.run( uvicorn.run(
"transcription.app:create_app", application,
factory=True, factory=settings.reload,
host=settings.host, host=settings.host,
port=settings.port, port=settings.port,
log_level=LOGGING_CONFIG.get("root", {}).get("level", "info").lower(), log_level=settings.log_level,
reload=settings.reload, reload=settings.reload,
) )
+10 -3
View File
@@ -6,6 +6,7 @@ are resolved by the provider adapters, not here.
""" """
import logging.config import logging.config
from collections.abc import Sequence
from enum import StrEnum from enum import StrEnum
from functools import cache from functools import cache
from pathlib import Path from pathlib import Path
@@ -51,7 +52,6 @@ class Settings(BaseSettings):
env_file=".env", env_file=".env",
env_file_encoding="utf-8", env_file_encoding="utf-8",
extra="ignore", extra="ignore",
cli_parse_args=True,
cli_implicit_flags=True, cli_implicit_flags=True,
cli_kebab_case=True, cli_kebab_case=True,
) )
@@ -99,8 +99,15 @@ class Settings(BaseSettings):
@cache @cache
def get_settings(**kwargs) -> Settings: def get_settings(**kwargs: Any) -> Settings:
return Settings(**kwargs) """Load cached settings without reading process CLI arguments."""
return Settings(_cli_parse_args=False, **kwargs)
def parse_cli_settings(args: Sequence[str] | None = None) -> Settings:
"""Load settings with CLI arguments at the executable boundary."""
cli_args = True if args is None else list(args)
return Settings(_cli_parse_args=cli_args)
LOGGING_CONFIG: dict[str, Any] = { LOGGING_CONFIG: dict[str, Any] = {