feat(render-spec): add list.row_action — t-paliad-163 Slice 1
Schema bump that lets the universal <FilterBar> tell shape-list which row interaction to wire (navigate / complete_toggle / approve / none). Defaults to navigate when empty so existing SystemView definitions and saved user views continue to render rows that route to the per-kind detail page. Validator extended; pure-Go test cases over every enum value + reject. TS mirror updated in client/views/types.ts. No DB migration — the field is purely additive on the JSON shape.
This commit is contained in:
@@ -71,10 +71,13 @@ export interface FilterSpec {
|
||||
|
||||
export type RenderShape = "list" | "cards" | "calendar";
|
||||
|
||||
export type ListRowAction = "navigate" | "complete_toggle" | "approve" | "none";
|
||||
|
||||
export interface ListConfig {
|
||||
columns?: string[];
|
||||
sort?: "date_asc" | "date_desc";
|
||||
density?: "comfortable" | "compact";
|
||||
row_action?: ListRowAction;
|
||||
}
|
||||
|
||||
export interface CardsConfig {
|
||||
|
||||
@@ -45,10 +45,23 @@ type RenderSpec struct {
|
||||
// ListConfig is the per-shape config for shape=list. Powers both the
|
||||
// /events table look (density=comfortable) and the activity-feed look
|
||||
// (density=compact + actor/time columns).
|
||||
//
|
||||
// RowAction tells shape-list which row interaction to wire when the
|
||||
// universal <FilterBar> renders the table. "navigate" (the default and
|
||||
// the contract for the existing /agenda/dashboard surfaces) routes a
|
||||
// row click to a per-kind detail page. "complete_toggle" is the
|
||||
// /events deadline-row pattern (checkbox + reopen button). "approve"
|
||||
// is the /inbox approver row (approve/reject buttons + revoke). "none"
|
||||
// is read-only (audit views, retrospective lists).
|
||||
//
|
||||
// shape-list.ts honours this when emitting the table's `entity-table`
|
||||
// classes — `entity-table--readonly` plus `none` skips the navigate
|
||||
// handler entirely.
|
||||
type ListConfig struct {
|
||||
Columns []string `json:"columns,omitempty"`
|
||||
Sort SortOrder `json:"sort,omitempty"`
|
||||
Density ListDensity `json:"density,omitempty"`
|
||||
Columns []string `json:"columns,omitempty"`
|
||||
Sort SortOrder `json:"sort,omitempty"`
|
||||
Density ListDensity `json:"density,omitempty"`
|
||||
RowAction ListRowAction `json:"row_action,omitempty"`
|
||||
}
|
||||
|
||||
// CardsConfig is the per-shape config for shape=cards.
|
||||
@@ -78,6 +91,29 @@ const (
|
||||
DensityCompact ListDensity = "compact"
|
||||
)
|
||||
|
||||
// ListRowAction identifies which row interaction the list-shape renderer
|
||||
// should wire. Defaults to RowActionNavigate when empty so existing
|
||||
// SystemView definitions and saved user views continue to render rows
|
||||
// that route to the per-kind detail page.
|
||||
type ListRowAction string
|
||||
|
||||
const (
|
||||
RowActionNavigate ListRowAction = "navigate"
|
||||
RowActionCompleteToggle ListRowAction = "complete_toggle"
|
||||
RowActionApprove ListRowAction = "approve"
|
||||
RowActionNone ListRowAction = "none"
|
||||
)
|
||||
|
||||
// KnownRowActions is the registry the validator checks against. Adding a
|
||||
// new action = add a const above AND append here AND extend
|
||||
// shape-list.ts's switch.
|
||||
var KnownRowActions = []ListRowAction{
|
||||
RowActionNavigate,
|
||||
RowActionCompleteToggle,
|
||||
RowActionApprove,
|
||||
RowActionNone,
|
||||
}
|
||||
|
||||
type CardsGroupBy string
|
||||
|
||||
const (
|
||||
@@ -148,6 +184,9 @@ func (c *ListConfig) validate() error {
|
||||
default:
|
||||
return fmt.Errorf("%w: unknown list.density %q", ErrInvalidInput, c.Density)
|
||||
}
|
||||
if c.RowAction != "" && !slices.Contains(KnownRowActions, c.RowAction) {
|
||||
return fmt.Errorf("%w: unknown list.row_action %q", ErrInvalidInput, c.RowAction)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,26 @@ func TestRenderSpec_CalendarViewEnum(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderSpec_RowActionEnum(t *testing.T) {
|
||||
for _, action := range KnownRowActions {
|
||||
t.Run(string(action), func(t *testing.T) {
|
||||
s := RenderSpec{Shape: ShapeList, List: &ListConfig{RowAction: action}}
|
||||
if err := s.Validate(); err != nil {
|
||||
t.Fatalf("known row_action %q must validate: %v", action, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
s := RenderSpec{Shape: ShapeList, List: &ListConfig{RowAction: "delete"}}
|
||||
if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
|
||||
t.Fatalf("unknown row_action must reject, got %v", err)
|
||||
}
|
||||
// Empty defaults to navigate at the renderer level — schema accepts.
|
||||
empty := RenderSpec{Shape: ShapeList, List: &ListConfig{}}
|
||||
if err := empty.Validate(); err != nil {
|
||||
t.Fatalf("empty row_action must validate (defaults to navigate): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderSpec_RoundTrip(t *testing.T) {
|
||||
original := RenderSpec{
|
||||
Shape: ShapeList,
|
||||
|
||||
Reference in New Issue
Block a user