from __future__ import annotations from dataclasses import dataclass from typing import Literal PortDisplaySide = Literal["left", "right"] @dataclass(frozen=True) class ComponentCategorySpec: """A presentation-only category declared by one component library.""" id: str label: str order: int = 0 def as_catalog_dict(self) -> dict[str, object]: return { "id": self.id, "label": self.label, "order": self.order, } @dataclass(frozen=True) class PortDisplaySpec: """Canvas placement for one port without changing its physical contract.""" name: str side: PortDisplaySide order: int = 0 @dataclass(frozen=True) class ComponentDisplaySpec: """Frontend metadata co-located with a component implementation.""" label: str library_id: str category_id: str symbol: str ports: tuple[PortDisplaySpec, ...] order: int = 0 @property def port_by_name(self) -> dict[str, PortDisplaySpec]: return {port.name: port for port in self.ports} @dataclass(frozen=True) class ComponentLibrarySpec: """Manifest for one explicitly enabled component library.""" id: str label: str version: str source_package: str categories: tuple[ComponentCategorySpec, ...] models: tuple[str, ...] temporary: bool = False order: int = 0 @property def category_by_id(self) -> dict[str, ComponentCategorySpec]: return {category.id: category for category in self.categories} def as_catalog_dict(self) -> dict[str, object]: return { "id": self.id, "label": self.label, "version": self.version, "sourcePackage": self.source_package, "temporary": self.temporary, "order": self.order, "categories": [ category.as_catalog_dict() for category in sorted( self.categories, key=lambda item: (item.order, item.id), ) ], }