You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Date: 2026-06-29 Status: Draft — open for discussion
Summary
This document analyses the design of the actions system across Krateo Composable Portal widgets. Three questions are addressed:
Schema uniformity — should all action-capable widgets share the same schema definition, or should each widget define its own?
Action coverage — which existing widgets should support actions but don't yet?
New action types — what new action types can be added while respecting the declarative pattern?
1. Current State
Four widgets currently define an actions object inside spec.widgetData:
Widget
Trigger property
Required?
Action types available
Button
clickActionId
yes
rest, navigate, openDrawer, openModal
Panel
clickActionId
no
rest, navigate, openDrawer, openModal
Form
submitActionId
yes
rest, navigate, openDrawer, openModal
Table
tableActions[].clickActionId
no
rest, navigate, openDrawer, openModal
All four schemas define the actions block identically — copy-pasted verbatim, ~270 lines of JSON each. TypeScript types are shared via src/types/Widget.d.ts and runtime dispatch is handled by the single useHandleAction hook (src/hooks/useHandleActions.ts).
2. Shared vs Per-Widget Schema Definition
Note on toolchain constraints:krateoctl currently does not support JSON Schema $defs / $ref resolution. Any schema-level deduplication — collapsing the repeated actions block into a shared definition — is therefore blocked by the CRD generation pipeline, not by frontend concerns. Improving $ref support in krateoctl is out of scope for this document and would be a separate discussion at the tooling level. The practical consequence is that the action schema remains duplicated across widget files for now; what follows is a discussion of whether that duplication reflects the right architectural stance, independent of how it might eventually be eliminated.
The architectural question
The real question is not just "how do we avoid copy-pasting?" but: is it correct to define the same action structure for all widgets, or should each widget have its own tailored definition?
Arguments for a uniform, shared structure (current approach)
Actions are operator intent, not widget semantics. The four action types (rest, navigate, openDrawer, openModal) describe what should happen in the system — they are agnostic of which widget triggers them. A Panel click and a Form submit are different interaction events, but they share the same outcome vocabulary.
The runtime is already uniform. The useHandleAction hook executes identically regardless of the triggering widget. The architecture already treats actions as a shared layer; the schema should reflect that.
Operators learn the pattern once. An operator who defines a rest action for a Button applies the same mental model to a Panel or a chart node. Consistency across widgets reduces cognitive load and makes the portal system feel coherent.
Easy to extend. Adding a new action type to the vocabulary automatically makes it available to all widgets, without per-widget decisions about whether to include it.
Trigger semantics are already widget-specific. The part that differs between widgets is the trigger property (clickActionId, submitActionId, tableActions[].clickActionId) — not the action definition itself. This is the right boundary: the widget owns the trigger; the action vocabulary is shared.
Arguments for widget-specific structures
Some action types are semantically awkward in certain contexts. A Form submitActionId executing a navigate type would be confusing — a submit is almost always a rest call. A widget-specific schema could enforce this.
Self-documenting. If a widget only declares the action types that make sense in its context, the schema communicates intent. An operator reading a FlowChart schema that only lists navigate and openDrawer understands the intended interaction model immediately.
Future trigger-specific payload shapes. A FlowChart node click carries node data; a BarChart bar click carries the bar's value and category. As payload shapes diverge, a shared definition may need per-widget overrides anyway.
Assessment
The uniformity argument is stronger for the current state of the system. The portal is operator-configured — restricting which action types a widget exposes means the platform makes decisions that belong to the operator. An operator who wants to trigger a rest call from a chart click (e.g., recording a selection event, or triggering a workflow) should be free to do so.
The practical downside of uniformity — schema duplication — is a toolchain problem, not an architectural one. Once the CRD generation pipeline supports $ref resolution, the duplication is mechanically fixable without any architectural change to how actions are designed.
Conclusion: keep the uniform structure. The trigger property is widget-specific; the action vocabulary is shared. Schema deduplication is deferred to a future improvement of the krateoctl pipeline.
3. Widget Action Audit
Already covered
Widget
Verdict
Button
✓ — primary interaction widget
Panel
✓ — card-level click
Form
✓ — submit trigger
Table
✓ — per-row actions via tableActions
Layout and structural widgets — no actions recommended
These widgets are composition containers. Their children own the interaction surface.
Widget
Reason
Row, Column, ButtonGroup
Layout only; no direct click surface
DataGrid
Container of other widgets; children own interaction
Page
Top-level wrapper; no interaction
RoutesLoader, Route
Configuration-only; no rendering
Theme
Styling configuration; no rendering
NavMenu, NavMenuItem
Navigation is their core function; wrapping it in a generic actions pattern would be circular
Display widgets — should gain actions
Since the action vocabulary is uniform (see Section 2), all action types should be available on these widgets. The operator decides which type is appropriate for the interaction — restricting the schema to a subset would remove valid use cases (e.g., triggering a rest call when clicking a FlowChart node to initiate a workflow, or acknowledging a notification via an API call).
Widget
Proposed trigger
Rationale
FlowChart
nodeClickActionId
Nodes represent K8s resources; click to inspect, navigate, or trigger an operation
PieChart
segmentClickActionId
Drill-down into a segment (e.g., click "Warning" → filtered event list, or trigger a rest action)
BarChart
barClickActionId
Same drill-down and action pattern as PieChart
EventList
eventClickActionId
View raw event detail, navigate to the affected resource, or trigger follow-up actions
Notifications
notificationClickActionId
Navigate to the originating resource, open a drawer, or acknowledge via REST
TabList
tabChangeActionId
Expose tab-change as an explicit navigation trigger for URL-driven tab state
Display widgets — actions not recommended
Widget
Reason
Markdown
Already has allowCopy / allowDownload as first-class props; adding generic actions would conflict with inline link semantics
Paragraph
Pure text display; if interaction is needed, wrap it in a Panel
YamlViewer
Display-only; built-in copy/download (like Markdown) is more appropriate than action-driven behavior
LineChart
Data points are too dense for reliable click targets; hover tooltips are a better UX fit
Filters
Filter state is managed internally; side effects belong in the widgets that consume filter output, not the filter widget itself
4. Proposed New Action Types
The existing four types (rest, navigate, openDrawer, openModal) cover the primary use cases well. The following new types fit naturally within the declarative, ID-driven pattern.
4.1 refresh — Invalidate and reload data
Explicitly trigger a TanStack Query cache invalidation without navigating away. Today, useHandleActions already calls queryClient.invalidateQueries() after every rest action; this type exposes refresh as a standalone trigger.
Use cases: Deep-link to Grafana dashboards, Argo CD, external Git repos from a Panel or Table row. Implementation note:url supports JQ interpolation using customPayload (e.g., row data from Table).
4.3 setState — Write to a shared cross-widget state key
Set a named key in a shared context store, driving dependent widgets on the same page without a navigation event. Generalises the existing prefix/Filters pattern to write access.
Use cases: Selecting a FlowChart node highlights related rows in an EventList; selecting a Table row updates a detail Panel on the same page. Implementation note: This is the most architecturally significant proposal. It requires a writable shared state context (similar to how Filters uses a prefix key for read access). This warrants a separate, deeper discussion before implementation.
Possible future additions
copy (copy a JQ-resolved value to the clipboard) and download (trigger a client-side file download) are plausible additions, but their primary use cases are already well covered by dedicated widget props — allowCopy and allowDownload on Markdown, for example. They are noted here for completeness but are not proposed as immediate work items.
5. Open Questions
Trigger naming convention — Current trigger props are clickActionId, submitActionId, tableActions[].clickActionId. Before adding more, should we align on a consistent convention? Proposal: {camelCaseEventName}ActionId (e.g., segmentClickActionId, nodeClickActionId).
setState scoping — If setState is adopted, what's its scope? Page-local? App-global? Keyed by a namespace prefix? The Filters prefix mechanism is useful prior art but was not designed for write access from arbitrary actions.
requireConfirmation implementation — Currently uses window.confirm() (a browser-native blocking dialog). As more widgets adopt actions, should this be upgraded to Modal.confirm() from Ant Design for visual consistency?
References
src/widgets/Button/Button.schema.json — reference schema for current action definitions
src/hooks/useHandleActions.ts — single runtime handler for all action types
src/types/Widget.d.ts — shared TypeScript action type definitions
docs/widgets-api-reference.md — public-facing API reference
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Widget Actions — Design Analysis
Date: 2026-06-29
Status: Draft — open for discussion
Summary
This document analyses the design of the
actionssystem across Krateo Composable Portal widgets. Three questions are addressed:1. Current State
Four widgets currently define an
actionsobject insidespec.widgetData:clickActionIdclickActionIdsubmitActionIdtableActions[].clickActionIdAll four schemas define the
actionsblock identically — copy-pasted verbatim, ~270 lines of JSON each. TypeScript types are shared viasrc/types/Widget.d.tsand runtime dispatch is handled by the singleuseHandleActionhook (src/hooks/useHandleActions.ts).2. Shared vs Per-Widget Schema Definition
The architectural question
The real question is not just "how do we avoid copy-pasting?" but: is it correct to define the same action structure for all widgets, or should each widget have its own tailored definition?
Arguments for a uniform, shared structure (current approach)
rest,navigate,openDrawer,openModal) describe what should happen in the system — they are agnostic of which widget triggers them. A Panel click and a Form submit are different interaction events, but they share the same outcome vocabulary.useHandleActionhook executes identically regardless of the triggering widget. The architecture already treats actions as a shared layer; the schema should reflect that.restaction for a Button applies the same mental model to a Panel or a chart node. Consistency across widgets reduces cognitive load and makes the portal system feel coherent.clickActionId,submitActionId,tableActions[].clickActionId) — not the action definition itself. This is the right boundary: the widget owns the trigger; the action vocabulary is shared.Arguments for widget-specific structures
submitActionIdexecuting anavigatetype would be confusing — a submit is almost always arestcall. A widget-specific schema could enforce this.navigateandopenDrawerunderstands the intended interaction model immediately.Assessment
The uniformity argument is stronger for the current state of the system. The portal is operator-configured — restricting which action types a widget exposes means the platform makes decisions that belong to the operator. An operator who wants to trigger a
restcall from a chart click (e.g., recording a selection event, or triggering a workflow) should be free to do so.The practical downside of uniformity — schema duplication — is a toolchain problem, not an architectural one. Once the CRD generation pipeline supports
$refresolution, the duplication is mechanically fixable without any architectural change to how actions are designed.Conclusion: keep the uniform structure. The trigger property is widget-specific; the action vocabulary is shared. Schema deduplication is deferred to a future improvement of the
krateoctlpipeline.3. Widget Action Audit
Already covered
tableActionsLayout and structural widgets — no actions recommended
These widgets are composition containers. Their children own the interaction surface.
actionspattern would be circularDisplay widgets — should gain actions
Since the action vocabulary is uniform (see Section 2), all action types should be available on these widgets. The operator decides which type is appropriate for the interaction — restricting the schema to a subset would remove valid use cases (e.g., triggering a
restcall when clicking a FlowChart node to initiate a workflow, or acknowledging a notification via an API call).nodeClickActionIdsegmentClickActionIdrestaction)barClickActionIdeventClickActionIdnotificationClickActionIdtabChangeActionIdDisplay widgets — actions not recommended
allowCopy/allowDownloadas first-class props; adding generic actions would conflict with inline link semantics4. Proposed New Action Types
The existing four types (
rest,navigate,openDrawer,openModal) cover the primary use cases well. The following new types fit naturally within the declarative, ID-driven pattern.4.1
refresh— Invalidate and reload dataExplicitly trigger a TanStack Query cache invalidation without navigating away. Today,
useHandleActionsalready callsqueryClient.invalidateQueries()after everyrestaction; this type exposes refresh as a standalone trigger.Use cases: A Button that triggers a data reload after an out-of-band operation; refreshing sibling widgets after a
restaction in a different widget.4.2
externalNavigate— Navigate to an external URLThe current
navigatetype routes within the SPA. AnexternalNavigatetype opens an external URL, with optional new-tab behavior.Use cases: Deep-link to Grafana dashboards, Argo CD, external Git repos from a Panel or Table row.
Implementation note:
urlsupports JQ interpolation usingcustomPayload(e.g., row data from Table).4.3
setState— Write to a shared cross-widget state keySet a named key in a shared context store, driving dependent widgets on the same page without a navigation event. Generalises the existing
prefix/Filters pattern to write access.Use cases: Selecting a FlowChart node highlights related rows in an EventList; selecting a Table row updates a detail Panel on the same page.
Implementation note: This is the most architecturally significant proposal. It requires a writable shared state context (similar to how Filters uses a
prefixkey for read access). This warrants a separate, deeper discussion before implementation.Possible future additions
copy(copy a JQ-resolved value to the clipboard) anddownload(trigger a client-side file download) are plausible additions, but their primary use cases are already well covered by dedicated widget props —allowCopyandallowDownloadon Markdown, for example. They are noted here for completeness but are not proposed as immediate work items.5. Open Questions
Trigger naming convention — Current trigger props are
clickActionId,submitActionId,tableActions[].clickActionId. Before adding more, should we align on a consistent convention? Proposal:{camelCaseEventName}ActionId(e.g.,segmentClickActionId,nodeClickActionId).setStatescoping — IfsetStateis adopted, what's its scope? Page-local? App-global? Keyed by a namespace prefix? The Filtersprefixmechanism is useful prior art but was not designed for write access from arbitrary actions.requireConfirmationimplementation — Currently useswindow.confirm()(a browser-native blocking dialog). As more widgets adopt actions, should this be upgraded toModal.confirm()from Ant Design for visual consistency?References
src/widgets/Button/Button.schema.json— reference schema for current action definitionssrc/hooks/useHandleActions.ts— single runtime handler for all action typessrc/types/Widget.d.ts— shared TypeScript action type definitionsdocs/widgets-api-reference.md— public-facing API referenceAll reactions