telega/dialog/types
Shared type definitions for the declarative dialog system.
A dialog is a set of windows; a window is a pure render function plus
event handlers. See telega/dialog for the builder API and the full
guide.
Types
A parsed button press delivered to on_action.
pub type ActionEvent {
ActionEvent(action_id: String, arg: option.Option(String))
}
Constructors
-
ActionEvent(action_id: String, arg: option.Option(String))
What a window handler decides to do next. state is always explicit:
handlers are pure functions from the old state to an action carrying the
new state.
pub type DialogAction(state) {
Stay(state)
Goto(window_id: String, state: state)
Back(state)
Done(state)
StartSub(
sub_id: String,
args: dict.Dict(String, String),
state: state,
)
}
Constructors
-
Stay(state)Re-render the current window with the new state.
-
Goto(window_id: String, state: state)Go to another window (pushed to history —
Backreturns here). -
Back(state)One step back in the navigation stack.
-
Done(state)Finish the dialog (runs
on_done, removes the keyboard). -
StartSub( sub_id: String, args: dict.Dict(String, String), state: state, )Start a sub-dialog attached with
dialog.subdialog. The sub takes over the live message; itsDonereturns control to the current window’son_sub_result(seetelega/dialog§ sub-dialogs).argsare handed to the sub-dialog’sinit. One level of nesting only:StartSubfrom inside a sub-dialog is rejected at runtime (logged, window re-rendered).
Validation errors returned by dialog.build().
pub type DialogBuildError {
DuplicateWindowId(id: String)
UnknownWindowReference(from: String, to: String)
CallbackDataTooLong(window: String, action: String, bytes: Int)
NoWindows
UnknownInitialWindow(id: String)
DuplicateWidgetId(window: String, id: String)
ReservedIdCharacter(kind: String, id: String)
DuplicateSubDialogId(id: String)
NestedSubDialog(id: String)
}
Constructors
-
DuplicateWindowId(id: String) -
UnknownWindowReference(from: String, to: String)A
Goto/widgetdone/on_sub_resulttarget references a window that doesn’t exist. -
CallbackDataTooLong(window: String, action: String, bytes: Int)The static part of a button’s callback data exceeds Telegram’s 64-byte limit. Use shorter dialog/window/action ids. For sub-dialog windows the budget includes the
<sub_id>.step prefix. -
NoWindows -
UnknownInitialWindow(id: String) -
DuplicateWidgetId(window: String, id: String) -
ReservedIdCharacter(kind: String, id: String):is the callback-data separator and is forbidden in dialog, window, action, and widget ids;.namespaces sub-dialog windows and is additionally forbidden in dialog and window ids. -
DuplicateSubDialogId(id: String) -
NestedSubDialog(id: String)The attached sub-dialog has sub-dialogs of its own — nesting is one level deep.
pub type DialogButton {
ActionButton(text: String, action_id: String)
ActionArgButton(text: String, action_id: String, arg: String)
UrlButton(text: String, url: String)
WebAppButton(text: String, url: String)
NoopButton(text: String)
}
Constructors
-
ActionButton(text: String, action_id: String)Action button: the engine builds callback data (
dlg:<dialog>:<window>:<action>). -
ActionArgButton(text: String, action_id: String, arg: String)Action with an argument (e.g. the id of a list item):
dlg:<dialog>:<window>:<action>:<arg>. -
UrlButton(text: String, url: String) -
WebAppButton(text: String, url: String) -
NoopButton(text: String)Non-clickable button (header/counter): rendered as a copy-text button that copies its own label.
Media attached to a window. media is a file_id or URL — uploading
local files via attach:// is not supported in dialogs yet (the string
will widen to FileOrString without breakage when it is).
pub type DialogMedia {
PhotoMedia(media: String, has_spoiler: Bool)
VideoMedia(media: String, has_spoiler: Bool)
AnimationMedia(media: String)
DocumentMedia(media: String)
}
Constructors
-
PhotoMedia(media: String, has_spoiler: Bool) -
VideoMedia(media: String, has_spoiler: Bool) -
AnimationMedia(media: String) -
DocumentMedia(media: String)
A managed keyboard widget: renders extra button rows for a window and
handles their events itself. Widget buttons use action ids of the form
w:<widget_id>:<cmd> (the w: action-id namespace is reserved for
widgets), so the engine can route presses to on_event without touching
the window’s on_action.
Built-in widgets live in telega/dialog/widget (pager, select, radio,
multiselect, paged_select). Custom widgets construct this record directly:
goto_targets— window ids the widget mayEmit(Goto(...))to; validated bydialog.build().static_actions— the widget’s full static action ids (w:<widget_id>:<cmd>) for the build-time callback-data byte budget; argument-carrying buttons are additionally validated on every render.
pub type KeyboardWidget(state, session, error, dependencies) {
KeyboardWidget(
id: String,
render: fn(WidgetCtx(state, session, error, dependencies)) -> List(
List(DialogButton),
),
on_event: fn(
WidgetCtx(state, session, error, dependencies),
String,
option.Option(String),
) -> Result(WidgetResult(state), error),
goto_targets: List(String),
static_actions: List(String),
)
}
Constructors
-
KeyboardWidget( id: String, render: fn(WidgetCtx(state, session, error, dependencies)) -> List( List(DialogButton), ), on_event: fn( WidgetCtx(state, session, error, dependencies), String, option.Option(String), ) -> Result(WidgetResult(state), error), goto_targets: List(String), static_actions: List(String), )
User-facing labels for engine-generated UI. All strings are resolved via
a fn(Context) -> Labels factory so telega_i18n works out of the box.
Defaults are wordless unicode symbols to stay locale-neutral.
pub type Labels {
Labels(
prev: String,
next: String,
page_info: String,
checked: String,
unchecked: String,
checkbox_on: String,
checkbox_off: String,
done: String,
stale: String,
)
}
Constructors
-
Labels( prev: String, next: String, page_info: String, checked: String, unchecked: String, checkbox_on: String, checkbox_off: String, done: String, stale: String, )Arguments
- stale
-
answer_callback_querytext shown when the user presses a button on an outdated dialog message.
The visible result of rendering a window: message text, an inline
keyboard, and an optional media attachment. The engine turns it into the
right send*/edit* calls (see telega/dialog/render) and builds
callback data for the buttons itself. For media windows the text becomes
the caption.
pub type RenderedWindow {
RenderedWindow(
text: format.FormattedText,
buttons: List(List(DialogButton)),
media: option.Option(DialogMedia),
)
}
Constructors
-
RenderedWindow( text: format.FormattedText, buttons: List(List(DialogButton)), media: option.Option(DialogMedia), )
Everything a widget sees when rendering or handling an event: the window’s user state, the widget’s own persistent store, the dialog labels, and the update context.
pub type WidgetCtx(state, session, error, dependencies) {
WidgetCtx(
state: state,
store: WidgetStore,
labels: Labels,
ctx: bot.Context(session, error, dependencies),
)
}
Constructors
-
WidgetCtx( state: state, store: WidgetStore, labels: Labels, ctx: bot.Context(session, error, dependencies), )
What a widget decides after handling one of its events.
pub type WidgetResult(state) {
StoreUpdated(WidgetStore)
Emit(DialogAction(state))
}
Constructors
-
StoreUpdated(WidgetStore)Update the widget store; the window is re-rendered in place.
-
Emit(DialogAction(state))Delegate a dialog action outward — e.g.
Gotoon a “done” press. To update the store and the state together, emitStay(new_state)after aStoreUpdatedround-trip or fold the value into the state itself.
A widget’s private key-value store. Persisted with the dialog instance
under __dialog_widget:<window_id>:<widget_id>, so widget state survives
restarts together with the rest of the dialog.
pub opaque type WidgetStore
A single dialog window: a pure render function plus event handlers.
rendermust be pure — its only “effect” is readingctx(e.g. for i18n). This is what makes windows snapshot-testable without a network.on_actionreceives an already-parsedActionEvent, not a raw string.on_text: Nonemeans text sent to this window is politely ignored: the engine just re-renders the window.widgetsare managed keyboard widgets (seetelega/dialog/widget): the engine appends their button rows afterrender’s own buttons and routes their events toKeyboardWidget.on_event, bypassingon_action.on_sub_resultruns when a sub-dialog started from this window (viaStartSub) finishes: it receives the window’s state and the result dict exported by thesubdialogattachment, and decides where to go next (Stayre-renders this window).Nonejust re-renders.
pub type Window(state, session, error, dependencies) {
Window(
id: String,
render: fn(state, bot.Context(session, error, dependencies)) -> RenderedWindow,
on_action: fn(
state,
ActionEvent,
bot.Context(session, error, dependencies),
) -> Result(DialogAction(state), error),
on_text: option.Option(
fn(state, String, bot.Context(session, error, dependencies)) -> Result(
DialogAction(state),
error,
),
),
widgets: List(
KeyboardWidget(state, session, error, dependencies),
),
on_sub_result: option.Option(
fn(
state,
dict.Dict(String, String),
bot.Context(session, error, dependencies),
) -> Result(DialogAction(state), error),
),
)
}
Constructors
-
Window( id: String, render: fn(state, bot.Context(session, error, dependencies)) -> RenderedWindow, on_action: fn( state, ActionEvent, bot.Context(session, error, dependencies), ) -> Result(DialogAction(state), error), on_text: option.Option( fn(state, String, bot.Context(session, error, dependencies)) -> Result( DialogAction(state), error, ), ), widgets: List( KeyboardWidget(state, session, error, dependencies), ), on_sub_result: option.Option( fn( state, dict.Dict(String, String), bot.Context(session, error, dependencies), ) -> Result(DialogAction(state), error), ), )
Values
pub fn default_labels() -> Labels
pub fn new_store() -> WidgetStore
pub fn store_get(
store store: WidgetStore,
key key: String,
) -> option.Option(String)
pub fn store_set(
store store: WidgetStore,
key key: String,
value value: String,
) -> WidgetStore