abstract class
UI::App
- UI::App
- Reference
- Object
Overview
Abstract base class for the declarative app + route registry; subclasses use the screen macro.
Defined in:
asset_pipeline/native_app.crClass Method Summary
-
.app_design_tokens : UI::DesignTokens::Tokens
If the consumer did not declare `design_tokens do ...
-
.bootstrap! : Nil
Default
.bootstrap!on the abstractUI::Appitself — no-op (no screens to register). -
.initial_route_id : Symbol
The route_id chosen as the navigation root.
-
.registration_for(route_id : Symbol) : ScreenRegistration
Look up a screen registration by route_id.
- .screens : Hash(Symbol, UI::App::ScreenRegistration)
Macro Summary
-
design_tokens(&block)
Optional: per-app design-token override block.
-
initial_route(route_id)
Declare the initial route on launch.
-
screen(route_id, controller = nil, screen_class = nil, web_controller = nil, web_path = nil, web_actions = nil)
Register a screen with its controller.
Class Method Detail
If the consumer did not declare design_tokens do ... end, this
default method returns the framework default. Method body is
compile-time emitted code, unaffected by the iOS class-init gap.
Default .bootstrap! on the abstract UI::App itself — no-op
(no screens to register). Subclasses override via the
macro inherited hook below, which uses macro finished to
enumerate every _bootstrap_screen_* method that the screen
macro emitted and emit explicit calls to each.
The route_id chosen as the navigation root. Override via the
initial_route macro. Default :_unset is a sentinel that the
dispatcher's launch_* helper detects and raises against.
Implemented as a method (not a class_getter ... = :_unset)
because class-var default initialisers are skipped under the iOS
class-init gap; method bodies are compile-time code unaffected
by the gap.
Look up a screen registration by route_id. Raises
UI::App::UnknownRouteError with the list of known routes if the
id is not registered.
Macro Detail
Optional: per-app design-token override block. The block receives
the default Tokens instance and must RETURN a (possibly
transformed) Tokens. Macro hygiene means we can't expose a local
variable named tokens for the block to reassign, so the contract
is "block in, transformed Tokens out".
design_tokens do |tokens|
tokens.copy_with(touch_target_minimum_px: 51.0)
end
design_tokens do |tokens|
tokens.with_brand(AcmeBrand.new)
end
Declare the initial route on launch.
class SpikeApp < UI::App
initial_route :sign_in
end
Register a screen with its controller. Screen class can be omitted —
convention is FooController -> FooScreen (under the same
namespace).
Phase 8B form (still works in 8C):
screen :sign_in, SignInController
screen :detail, DetailController, screen_class: CustomScreen
Phase 8C — web-only or dual-target screens. The positional
controller arg is optional (defaults to nil); screen_class is
the third positional (preserves Phase 8B's screen :foo, FooCtrl, CustomScreen form). The new web kwargs (web_controller:,
web_path:, web_actions:) follow and are passed by name. The
brief proposed a * kwarg-only separator before the web kwargs,
but Crystal macros cannot combine *, with a default value on a
positional arg before it — every call that omits the positional
fails with "wrong number of arguments". Without the *, the
web kwargs are kwarg-by-default at the call site (the kwarg name
disambiguates from positional misuse) and the type system catches
positional confusion downstream (Class refs for web_controller,
String for web_path, Array(NamedTuple) for web_actions).
# Web-only — no native side yet.
screen :sign_in,
web_controller: SignInController,
web_path: "/",
web_actions: [
{verb: :get, action: :index},
{verb: :post, action: :submit, path: "/sign_in/submit"},
]
# Dual-target — same class is the native AND web controller.
# (In the spike, SignInController is `< Amber::Controller::Base`
# only — Phase 8A's parallel-controllers shape. A future spike
# may show a native UI::Controller and Amber::Controller::Base
# passed separately.)
screen :todos, TodosController,
web_controller: TodosController,
web_path: "/todos"
# Web-only shortcut — bare web_path defaults web_actions to
# `[{verb: :get, action: :index, path: web_path}]`.
screen :about, web_controller: AboutController, web_path: "/about"
Defaulting and validation (all enforced at macro-expansion time):
- If
controller,web_controller,web_path, andweb_actionsare all unset →{% raise %}(registration must declare at least one side). - If any web binding is set,
web_controllerMUST be set →{% raise %}otherwise (web_pathorweb_actionswithout aweb_controlleris meaningless). - If
web_actionsis non-empty, every entry must either carry its ownpath:ORweb_pathmust be set as the default →{% raise %}otherwise. - If
web_actionsis empty ANDweb_pathis set → defaultweb_actionsto[{verb: :get, action: :index, path: web_path}]. Note on signature: the brief proposed a*kwarg-only separator before the new web kwargs, but Crystal macros cannot combine a*separator with a default value on a positional arg before it ("wrong number of arguments" at every call site that omits the positional). To preserve Phase 8B's call shapes EXACTLY, the parameter order keepsscreen_classas the third positional (immediately aftercontroller) so old calls of the formscreen :foo, FooController, CustomScreencontinue to bindCustomScreentoscreen_class. The new web kwargs follow. Codex iter-1 finding identified that re-ordering screen_class past the web kwargs silently re-bound the third positional — this reordering pins the legacy form.