Package Layers
lazy-cuh should keep the current primitive packages and add higher-level
packages above them. The goal is not more folders for their own sake. The goal
is to make the abstraction layers visible in code:
- primitives for correctness and escape hatches
- builders for typed declarations and derived wiring
- presets for strong defaults
- composition helpers for complete lazygit-style screens
- widgets as the Textual adapter boundary
Decision
Section titled “Decision”Keep the existing focused primitive packages:
lazy_cuh/ core/ inputs/ keybar/ layout/ modals/ styles/ viewport/ view/ widgets/Add these packages when the corresponding abstractions are extracted from the examples:
lazy_cuh/ builders/ options.py actions.py shell.py
presets/ keys.py shell.py themes.py
composition/ shell.pyFacade modules are the public entry points for a domain. They may re-export focused implementation modules so users get a stable import path while the internals stay small:
lazy_cuh/ builders/ options.py # public facade: lc.builders.options.* option_fields.py # field constructors option_spec.py # OptionsSpec and view/edit derivation
composition/ shell.py # public facade: lc.composition.shell.* shell_panels.py # panel/tab declarations shell_navigation.py # focus graph declarations shell_action_models.py # shell action behavior shell_bindings.py # keybinding declarations and presets shell_spec.py # ShellSpec orchestration shell_runtime.py # runtime/Textual wiringOnly facade modules should be treated as the stable user-facing import path unless another implementation module is explicitly documented as public.
The package responsibilities should be:
core: plain data, commands, events, selection, and semantic itemsinputs: key sequences, binding registries, input resolution, and key profileslayout: row composition, wrapping, and line numbersviewport: visual-line mapping and scroll policyview: list, tree, text, panel, tab, and shell view modelskeybar: keybar models, hint groups, and renderersmodals: modal specs and pure modal state transitionsstyles: palettes, roles, and reusable default CSSwidgets: Textual adapters onlybuilders: typed declarations that derive primitive models and wiringpresets: reusable defaults for keys, themes, shell behavior, and common lazygit-style choicescomposition: app-shell helpers that assemble panels, tabs, focus, keybars, runtime wiring, and widgets for common layouts
Public Import Style
Section titled “Public Import Style”Layered packages should be imported in a way that keeps the layer and domain visible at the call site.
Prefer:
import lazy_cuh as lc
registry = lc.builders.actions.registry(actions, aliases=aliases)panel = lc.composition.shell.panel("main", index=1)Avoid flattening this into a long root-level helper:
from lazy_cuh import registry_from_action_mapAlso avoid hiding the layer behind app-local aliases in examples:
from lazy_cuh.builders import actions as action_buildersUse lc.inputs for key and action primitives. The plural package name avoids
shadowing Python’s built-in input() in normal examples.
Short function names are acceptable when the module path carries the meaning.
lc.builders.actions.registry(...) is clearer than a long function name because
it reads as:
framework namespace -> builder layer -> action domain -> registry outputThe same pattern should apply as the higher-level packages grow:
import lazy_cuh as lc
options = lc.builders.options.declare(...)keys = lc.presets.keys.lazy()shell = lc.composition.shell.declare(...)The exact helper names may change while the project is pre-alpha, but the shape should remain: short names inside explicit layer/domain modules.
Tiny documentation snippets may still use direct imports when that makes the example shorter:
from lazy_cuh.view import ListViewModelFull examples should prefer the lc namespace so readers learn where each
concept belongs.
Why This Shape
Section titled “Why This Shape”The current primitive packages are already useful and testable. Replacing them with one large component tree would create churn without solving the main problem: app authors still need too much glue for normal screens.
The missing layer is above the primitives. For example, an option field declaration should be able to derive:
- rendered rows
- parser and formatter behavior
- validation
- input modal specs
- immutable update handling
- keybar/help metadata where relevant
That belongs in builders, not in view, widgets, or the example app.
Presets belong in a separate package because they are opinionated defaults, not core behavior. A user should be able to use lazy-cuh without the default lazygit-style key profile, theme, or shell choices.
The first preset domains are lc.presets.keys, lc.presets.navigation, and
lc.presets.shell. keys owns reusable key defaults such as leader-key aliases
and movement-direction maps. navigation owns item-navigation defaults that
need to stay aligned across keymaps, keybars, docs, and examples. shell owns
opinionated shell binding groups for panel focus, spatial focus, help, and
quit. lc.presets.shell.lazygit(...) can use bare panel numbers, while
lc.presets.shell.lazyvim(...) keeps panel numbers behind a leader so bare
digits can remain count prefixes. Movement presets should include vim keys and
accessible alternatives where practical, for example both h/j/k/l and arrow
keys for directional focus.
Composition helpers belong above builders and presets. They should remove boring app-shell wiring, but they should not own app domain state. The shell declaration API belongs there because it coordinates panels, focus graph edges, shell commands, tab commands, keybar contexts, and runtime composition as one app-shell concept.
Rejected Alternatives
Section titled “Rejected Alternatives”Full Layer Restructure
Section titled “Full Layer Restructure”lazy_cuh/ primitives/ builders/ adapters/ presets/This is conceptually clean, but it would make imports noisier and force large churn before the higher-level API is proven.
Component-First Layout
Section titled “Component-First Layout”lazy_cuh/ components/ list/ tree/ options/ shell/This is friendly at first, but it risks mixing pure models, rendering, navigation, and Textual widgets in the same package. Lazy-cuh should keep those concerns separate.
Top-Level Convenience Modules Only
Section titled “Top-Level Convenience Modules Only”lazy_cuh/ options.py shell.py actions.pyThis makes imports short, but it hides the architecture and tends to create large modules over time. Top-level exports are fine as convenience imports, but they should not be the main structure.
Rule Of Thumb
Section titled “Rule Of Thumb”When extracting from examples:
- repeated primitive construction moves to
builders - opinionated defaults move to
presets - complete shell wiring moves to
composition - Textual lifecycle code stays in
widgets - app-specific state stays in the downstream app
If a package starts importing app-specific concepts, it is in the wrong layer.