Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
bc8708a
feat(taskflow): pass consumer-owned feedback field for display messag…
chathura-de-silva Sep 30, 2026
16b0f41
docs(taskflow): document the pass-through messages field on handles
chathura-de-silva Sep 30, 2026
23a808d
feat(taskflow): pass consumer-owned feedback field for display messag…
chathura-de-silva Sep 30, 2026
49f6d35
docs(taskflow): document the pass-through messages field on handles
chathura-de-silva Sep 30, 2026
396a8ca
Merge branch 'feat/250-handle-declared-per-state-messages-for-post-ac…
chathura-de-silva Oct 5, 2026
239aa86
Merge remote-tracking branch 'upstream/main' into feat/250-handle-dec…
chathura-de-silva Oct 6, 2026
3ddb31a
docs(taskflow): say messages keeps its JSON value, not its exact bytes
chathura-de-silva Oct 6, 2026
21d8801
feat(taskflow): pass consumer-owned feedback field for display messag…
chathura-de-silva Sep 30, 2026
32407f5
docs(taskflow): document the pass-through messages field on handles
chathura-de-silva Sep 30, 2026
4449291
docs(taskflow): say messages keeps its JSON value, not its exact bytes
chathura-de-silva Oct 6, 2026
e6212b8
Merge branch 'feat/250-handle-declared-per-state-messages-for-post-ac…
chathura-de-silva Oct 6, 2026
8de10b7
Merge remote-tracking branch 'upstream/main' into feat/250-handle-dec…
chathura-de-silva Oct 7, 2026
e24390a
Merge remote-tracking branch 'upstream/main' into feat/250-handle-dec…
chathura-de-silva Oct 8, 2026
2e9207b
Merge remote-tracking branch 'upstream/main' into feat/250-handle-dec…
chathura-de-silva Oct 12, 2026
d66a13d
Merge remote-tracking branch 'upstream/main' into feat/250-handle-dec…
chathura-de-silva Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions taskflow/docs/frontend-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ The `zoneview` renderer (see [template reference](template-reference.md#the-zone
- `id` is the section's key in the render config. It is unique within the view.
- `title` is omitted when the section has none.
- `handles` lists the buttons legal in the current state; it is omitted when there are none, which means the entry is read-only.
- A handle's `messages` is present only when the render config declares one, and its JSON value arrives unchanged, though encoding may compact whitespace and escape `<`, `>` and `&`. Its shape is defined by your frontend, not by the renderer: it's the place to say what to tell the user once the command has been dispatched.
- An empty view is `[]`.

The frontend renders the entries top to bottom, picking a widget by `type`.
Expand Down
2 changes: 1 addition & 1 deletion taskflow/docs/template-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ The interface (`Renderer.Render(context.Context, json.RawMessage, Facts) (json.R
}
```

- **`sections`** — what can be shown. `visibleWhen` decides whether a section is visible for the current state, data and claims; `handles` are the buttons it offers.
- **`sections`** — what can be shown. `visibleWhen` decides whether a section is visible for the current state, data and claims; `handles` are the buttons it offers. A handle has a `command`, a `label`, an optional `element` owned by the section's renderer, and an optional `messages`: the feedback the consumer shows once the command has been dispatched. `messages` is passed through to the view as the same JSON value, though encoding may compact whitespace and escape `<`, `>` and `&`.
- **`layouts`** — named orderings. Each is a permutation of **all** section keys and expresses relative order only, never visibility. States that agree on the relative order of the sections they show can share one layout.
- **`states`** — per state, `actions` lists the commands legal in it (a section's handle is sent only if its command is listed), and `order` references the layout to render in. The reference must have the form `#/layouts/<name>` and name a defined layout, or rendering fails.

Expand Down
36 changes: 36 additions & 0 deletions taskflow/renderer/zoneview/renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,42 @@ func TestRender_CarriesTitleAndLegalHandles(t *testing.T) {
}
}

// A legal handle carries its messages through, whatever
// their shape, and a handle that declares none emits no "messages" key.
func TestRender_PassesHandleMessagesThrough(t *testing.T) {
r := newTestRenderer(t)
const config = `{
"id": "test:render",
"sections": {
"user_form": {
"templateId": "t", "title": "User Form", "projector": "MARKDOWN",
"handles": [
{ "command": "save_as_draft", "label": "Save as Draft" },
{ "command": "submit", "label": "Submit",
"messages": { "QUEUED_EXTERNALLY": { "text": "Sent for review.", "variant": "success" } } }
]
}
},
"states": {
"PENDING_USER": { "actions": [{ "command": "save_as_draft" }, { "command": "submit" }] }
}
}`

view := decodeView(t, render(t, r, config, tfrenderer.Facts{State: "PENDING_USER"}))
if len(view) != 1 {
t.Fatalf("got %d components %v, want 1", len(view), view)
}
got, err := json.Marshal(view[0].Handles)
if err != nil {
t.Fatalf("marshal handles: %v", err)
}
const want = `[{"command":"save_as_draft","label":"Save as Draft"},` +
`{"command":"submit","label":"Submit","messages":{"QUEUED_EXTERNALLY":{"text":"Sent for review.","variant":"success"}}}]`
if string(got) != want {
t.Errorf("got handles %s, want %s", got, want)
}
}

// With no render config there is nothing to show: an empty list
func TestRender_EmptyConfigReturnsEmptyList(t *testing.T) {
r := newTestRenderer(t)
Expand Down
11 changes: 7 additions & 4 deletions taskflow/renderer/zoneview/zoneview.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ type Action struct {
// screen element. Command joins this claim to a state-level Action; Label is
// the user-facing text; Element is a free-form identifier owned by the
// section's renderer (e.g. "primary_action", "secondary_action" for a FORM
// projector) — the data layer does not interpret it.
// projector) — the data layer does not interpret it. Messages is the feedback
// the consumer shows once the command has been dispatched; its shape is owned
// by the consumer and it is passed through uninterpreted.
//
// "Claim" here means a section laying claim to a command. It is unrelated to
// the authorization claims in renderer.Facts.Claims, which decide whether a
// section is rendered at all.
type HandleClaim struct {
Command string `json:"command"`
Label string `json:"label"`
Element string `json:"element,omitempty"`
Command string `json:"command"`
Label string `json:"label"`
Element string `json:"element,omitempty"`
Messages json.RawMessage `json:"messages,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we enforce the type more specific?

Suggested change
Messages json.RawMessage `json:"messages,omitempty"`
Messages map[string]HandleMessage `json:"messages,omitempty"`

We can define the Message struct ads follows:

type HandleMessage struct {
    Text    string `json:"text"`
    Variant string `json:"variant,omitempty"`
}

@chathura-de-silva chathura-de-silva Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we enforce the type more specific?

We can define the Message struct ads follows:

type HandleMessage struct {
    Text    string `json:"text"`
    Variant string `json:"variant,omitempty"`
}

I intentionally avoided doing this to allow the consumer to decide the format and keep core away from deciding what is/what type the feedback format is. Will do if needed (with Mushrafs feedback as you said.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This make sense. Downstream can define what's the config they willing to have. Let's wait for review from others.

}

// SectionView is the per-zone trader-app metadata read from render.json
Expand Down
Loading