@almadar/std 16.187.0 → 16.188.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/behaviors/lolo/ui/core/.hand-authored.json +4 -0
- package/behaviors/lolo/ui/core/atoms/std-board.lolo +138 -4
- package/behaviors/lolo/ui/core/atoms/std-browse.lolo +148 -8
- package/behaviors/lolo/ui/core/atoms/std-calendar.lolo +141 -3
- package/behaviors/lolo/ui/core/atoms/std-event-log.lolo +255 -2
- package/behaviors/lolo/ui/core/atoms/std-gallery.lolo +148 -1
- package/behaviors/lolo/ui/core/atoms/std-list.lolo +193 -1
- package/behaviors/lolo/ui/core/atoms/std-record-detail.lolo +329 -0
- package/behaviors/lolo/ui/core/atoms/std-selection.lolo +182 -1
- package/behaviors/lolo/ui/core/atoms/std-tree.lolo +113 -3
- package/behaviors/lolo/ui/game/atoms/std-area-fill.lolo +93 -0
- package/behaviors/lolo/ui/game/atoms/std-loop-lock.lolo +79 -0
- package/behaviors/lolo/ui/game/atoms/std-pad-lock.lolo +93 -0
- package/behaviors/lolo/ui/game/atoms/std-patrol-hazard.lolo +14 -7
- package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +21 -4
- package/behaviors/lolo/ui/game/atoms/std-velocity-gate.lolo +101 -0
- package/behaviors/registry/infra/atoms/std-approval-gate.orb +16 -8
- package/behaviors/registry/infra/atoms/std-cache-aside.orb +62 -31
- package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +32 -16
- package/behaviors/registry/infra/atoms/std-data-erasure.orb +16 -8
- package/behaviors/registry/infra/atoms/std-mod-queue.orb +16 -8
- package/behaviors/registry/infra/atoms/std-rate-limiter.orb +28 -14
- package/behaviors/registry/infra/organisms/std-status-lifecycle-control.orb +2 -1
- package/behaviors/registry/ml/organisms/std-escalating-decision.orb +10 -5
- package/behaviors/registry/ml/organisms/std-knowledge-tracing.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +2 -1
- package/behaviors/registry/ui/core/atoms/std-board.orb +355 -2
- package/behaviors/registry/ui/core/atoms/std-browse.orb +304 -31
- package/behaviors/registry/ui/core/atoms/std-calendar.orb +344 -0
- package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +2 -1
- package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-esign-request.orb +6 -3
- package/behaviors/registry/ui/core/atoms/std-event-log.orb +773 -0
- package/behaviors/registry/ui/core/atoms/std-export.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-gallery.orb +390 -0
- package/behaviors/registry/ui/core/atoms/std-import.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-list.orb +451 -0
- package/behaviors/registry/ui/core/atoms/std-record-detail.orb +1141 -0
- package/behaviors/registry/ui/core/atoms/std-selection.orb +436 -0
- package/behaviors/registry/ui/core/atoms/std-tree.orb +292 -1
- package/behaviors/registry/ui/game/atoms/std-area-fill.orb +955 -0
- package/behaviors/registry/ui/game/atoms/std-fx-trail.orb +1 -1
- package/behaviors/registry/ui/game/atoms/std-gate-lock.orb +1 -1
- package/behaviors/registry/ui/game/atoms/std-lives.orb +1 -1
- package/behaviors/registry/ui/game/atoms/std-loop-lock.orb +785 -0
- package/behaviors/registry/ui/game/atoms/std-pad-lock.orb +903 -0
- package/behaviors/registry/ui/game/atoms/std-patrol-hazard.orb +190 -44
- package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +868 -637
- package/behaviors/registry/ui/game/atoms/std-velocity-gate.orb +1040 -0
- package/behaviors/registry/ui/game/organisms/std-arcade-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-crew-board-2d.orb +6 -3
- package/behaviors/registry/ui/game/organisms/std-dungeon-board-2d.orb +10 -5
- package/behaviors/registry/ui/game/organisms/std-market-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-objective-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +7 -4
- package/behaviors/registry/ui/game/organisms/std-survival-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-tactics-board-2d.orb +12 -6
- package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-worldmap-board-2d.orb +6 -3
- package/behaviors/registry/ui/learning/atoms/std-learn-slope-probe.orb +1 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-article.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-footer.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-split.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-stats.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-steps.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-team.orb +2 -1
- package/dist/behaviors/exports-reader.js +7541 -3153
- package/dist/behaviors/functions/index.d.ts +598 -18
- package/dist/behaviors/functions/index.js +7524 -3154
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +7542 -3154
- package/dist/behaviors/query.js +7541 -3153
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +485 -461
- package/dist/behaviors-registry.json +2612 -934
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7542 -3154
- package/dist/knob-embeddings.hash.json +3 -3
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
app std-selection "1.0.0"
|
|
2
2
|
"std-selection as a Function"
|
|
3
|
+
type FilterSpec = { field : string!, label : string, options : [string] }
|
|
3
4
|
orbital SelectableItemOrbital {
|
|
4
5
|
type SelectableItemLoaded = Event { data : [SelectableItem] } "Fired when SelectableItem finishes loading; payload.data holds the list"
|
|
5
6
|
|
|
@@ -46,7 +47,37 @@ orbital SelectableItemOrbital {
|
|
|
46
47
|
direction: horizontal
|
|
47
48
|
gap: sm
|
|
48
49
|
type: stack
|
|
49
|
-
},
|
|
50
|
+
}, (if @config.bodySearch {
|
|
51
|
+
children: [{
|
|
52
|
+
className: "w-full max-w-md"
|
|
53
|
+
clearable: true
|
|
54
|
+
event: REFETCH_QUERY
|
|
55
|
+
placeholder: @config.searchPlaceholder
|
|
56
|
+
type: search-input
|
|
57
|
+
}]
|
|
58
|
+
direction: horizontal
|
|
59
|
+
gap: sm
|
|
60
|
+
type: stack
|
|
61
|
+
} {
|
|
62
|
+
children: []
|
|
63
|
+
gap: none
|
|
64
|
+
type: stack
|
|
65
|
+
}), (if [">", (array/len @config.filters), 0] {
|
|
66
|
+
children: [{
|
|
67
|
+
entity: SelectableItem
|
|
68
|
+
event: REFETCH_FILTER
|
|
69
|
+
filters: @config.filters
|
|
70
|
+
look: @config.filterBarLook
|
|
71
|
+
type: filter-group
|
|
72
|
+
}]
|
|
73
|
+
direction: horizontal
|
|
74
|
+
gap: sm
|
|
75
|
+
type: stack
|
|
76
|
+
} {
|
|
77
|
+
children: []
|
|
78
|
+
gap: none
|
|
79
|
+
type: stack
|
|
80
|
+
}), { type: divider }, {
|
|
50
81
|
color: muted
|
|
51
82
|
content: "Choose a selectableitem to continue."
|
|
52
83
|
type: typography
|
|
@@ -61,6 +92,7 @@ orbital SelectableItemOrbital {
|
|
|
61
92
|
label: Select
|
|
62
93
|
}]
|
|
63
94
|
look: @config.tableLook
|
|
95
|
+
pageSize: @config.displayPageSize
|
|
64
96
|
renderItem: (fn item {
|
|
65
97
|
align: center
|
|
66
98
|
children: [{ label: @item.name, type: checkbox }]
|
|
@@ -105,6 +137,106 @@ orbital SelectableItemOrbital {
|
|
|
105
137
|
}
|
|
106
138
|
|
|
107
139
|
state idle {
|
|
140
|
+
;; REFETCH_* stays in `idle` and DOES NOT replace the pick-list with a
|
|
141
|
+
;; spinner — same reasoning as std-browse's REFETCH_* arms: a skeleton
|
|
142
|
+
;; on refetch unmounts the list and flickers every keystroke. The fetch
|
|
143
|
+
;; fires in the background; the SelectableItemLoaded arm below
|
|
144
|
+
;; re-renders in place once it lands.
|
|
145
|
+
REFETCH_QUERY -> idle
|
|
146
|
+
(fetch SelectableItem {
|
|
147
|
+
emit: { failure: SelectableItemLoadFailed, success: SelectableItemLoaded }
|
|
148
|
+
filter: (or ["=", ?searchTerm, ""] (str/includes (object/get @entity name) ?searchTerm))
|
|
149
|
+
})
|
|
150
|
+
REFETCH_FILTER -> idle
|
|
151
|
+
(fetch SelectableItem {
|
|
152
|
+
emit: { failure: SelectableItemLoadFailed, success: SelectableItemLoaded }
|
|
153
|
+
filter: (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value])
|
|
154
|
+
})
|
|
155
|
+
;; No filter carried over on REFETCH_PAGE, mirroring std-browse exactly.
|
|
156
|
+
;; Paging is safe here: this atom's selection is a single transient id
|
|
157
|
+
;; carried only in the SELECT/CONFIRM_SELECTION payload (`?id`), never
|
|
158
|
+
;; accumulated into a persisted @entity array, so turning a page cannot
|
|
159
|
+
;; strand an "already selected" item — there is no multi-item selection
|
|
160
|
+
;; state to strand. A future multi-select variant that DOES accumulate
|
|
161
|
+
;; picks into an @entity field would need to carry that array across
|
|
162
|
+
;; REFETCH_PAGE explicitly; this single-pick atom does not need to.
|
|
163
|
+
REFETCH_PAGE -> idle
|
|
164
|
+
(fetch SelectableItem {
|
|
165
|
+
emit: { failure: SelectableItemLoadFailed, success: SelectableItemLoaded }
|
|
166
|
+
limit: @config.pageSize
|
|
167
|
+
offset: ["*", ["-", ?page, 1], @config.pageSize]
|
|
168
|
+
})
|
|
169
|
+
SelectableItemLoaded -> idle
|
|
170
|
+
(render-ui main {
|
|
171
|
+
children: [{
|
|
172
|
+
align: center
|
|
173
|
+
children: [{ name: check-square, type: icon }, {
|
|
174
|
+
content: SelectableItems
|
|
175
|
+
type: typography
|
|
176
|
+
variant: h2
|
|
177
|
+
}]
|
|
178
|
+
direction: horizontal
|
|
179
|
+
gap: sm
|
|
180
|
+
type: stack
|
|
181
|
+
}, (if @config.bodySearch {
|
|
182
|
+
children: [{
|
|
183
|
+
className: "w-full max-w-md"
|
|
184
|
+
clearable: true
|
|
185
|
+
event: REFETCH_QUERY
|
|
186
|
+
placeholder: @config.searchPlaceholder
|
|
187
|
+
type: search-input
|
|
188
|
+
}]
|
|
189
|
+
direction: horizontal
|
|
190
|
+
gap: sm
|
|
191
|
+
type: stack
|
|
192
|
+
} {
|
|
193
|
+
children: []
|
|
194
|
+
gap: none
|
|
195
|
+
type: stack
|
|
196
|
+
}), (if [">", (array/len @config.filters), 0] {
|
|
197
|
+
children: [{
|
|
198
|
+
entity: SelectableItem
|
|
199
|
+
event: REFETCH_FILTER
|
|
200
|
+
filters: @config.filters
|
|
201
|
+
look: @config.filterBarLook
|
|
202
|
+
type: filter-group
|
|
203
|
+
}]
|
|
204
|
+
direction: horizontal
|
|
205
|
+
gap: sm
|
|
206
|
+
type: stack
|
|
207
|
+
} {
|
|
208
|
+
children: []
|
|
209
|
+
gap: none
|
|
210
|
+
type: stack
|
|
211
|
+
}), { type: divider }, {
|
|
212
|
+
color: muted
|
|
213
|
+
content: "Choose a selectableitem to continue."
|
|
214
|
+
type: typography
|
|
215
|
+
variant: caption
|
|
216
|
+
}, {
|
|
217
|
+
className: "transition-shadow hover:shadow-md cursor-pointer"
|
|
218
|
+
entity: ?data
|
|
219
|
+
fields: []
|
|
220
|
+
itemActions: [{
|
|
221
|
+
event: SELECT
|
|
222
|
+
icon: check
|
|
223
|
+
label: Select
|
|
224
|
+
}]
|
|
225
|
+
look: @config.tableLook
|
|
226
|
+
pageSize: @config.displayPageSize
|
|
227
|
+
renderItem: (fn item {
|
|
228
|
+
align: center
|
|
229
|
+
children: [{ label: @item.name, type: checkbox }]
|
|
230
|
+
direction: horizontal
|
|
231
|
+
gap: sm
|
|
232
|
+
type: stack
|
|
233
|
+
})
|
|
234
|
+
type: data-grid
|
|
235
|
+
}]
|
|
236
|
+
direction: vertical
|
|
237
|
+
gap: lg
|
|
238
|
+
type: stack
|
|
239
|
+
})
|
|
108
240
|
SELECT -> selecting
|
|
109
241
|
(render-ui main {
|
|
110
242
|
children: [{
|
|
@@ -269,6 +401,25 @@ orbital SelectableItemOrbital {
|
|
|
269
401
|
@description "Indicates a selectable item failed to load."
|
|
270
402
|
@synonyms "error, failure, problem, load error"
|
|
271
403
|
@tier "internal"
|
|
404
|
+
REFETCH_QUERY {
|
|
405
|
+
searchTerm : string!
|
|
406
|
+
}
|
|
407
|
+
@description "Triggers a pick-list refresh based on the current search term."
|
|
408
|
+
@synonyms "refresh, reload, update, re-query, search"
|
|
409
|
+
@tier "domain"
|
|
410
|
+
REFETCH_FILTER {
|
|
411
|
+
field : string!
|
|
412
|
+
value : string!
|
|
413
|
+
}
|
|
414
|
+
@description "Triggers a pick-list refresh based on current filter criteria."
|
|
415
|
+
@synonyms "refresh, filter, update, reload"
|
|
416
|
+
@tier "domain"
|
|
417
|
+
REFETCH_PAGE {
|
|
418
|
+
page : number!
|
|
419
|
+
}
|
|
420
|
+
@description "Triggers a pick-list page refresh."
|
|
421
|
+
@synonyms "reload, refresh, update, paginate"
|
|
422
|
+
@tier "domain"
|
|
272
423
|
}
|
|
273
424
|
|
|
274
425
|
config {
|
|
@@ -276,6 +427,36 @@ orbital SelectableItemOrbital {
|
|
|
276
427
|
@label "Table look"
|
|
277
428
|
@description "Layer 2 visual treatment for the data table rendered by this atom."
|
|
278
429
|
@tier "presentation"
|
|
430
|
+
bodySearch : boolean = false
|
|
431
|
+
@label "Show the pick-list's own search box?"
|
|
432
|
+
@description "Embeds a search box above the pick-list. Set true only when the page has no other search affordance (e.g. no page-level std-search), so the surface shows one search box instead of two. False is the default — this atom has never had a search box (0 consumers today)."
|
|
433
|
+
@synonyms "inline search, list search, built-in search, show search"
|
|
434
|
+
@tier "presentation"
|
|
435
|
+
searchPlaceholder : string = "Search…"
|
|
436
|
+
@label "Search box placeholder text"
|
|
437
|
+
@description "Hint text inside the pick-list search box."
|
|
438
|
+
@synonyms "search hint, placeholder, search box text"
|
|
439
|
+
@tier "presentation"
|
|
440
|
+
filters : [FilterSpec] = []
|
|
441
|
+
@label "Which filters should appear above the pick-list?"
|
|
442
|
+
@description "Dropdown filter facets by field, rendered as an embedded filter bar above the rows; each selection fires REFETCH_FILTER on this trait. Empty (the default) = no filter bar, the right setting whenever the page already composes std-filter beside this list and routes XFilter.FILTER -> REFETCH_FILTER — that is the wired owner and remains the primary path. Use this knob only for a standalone pick-list with no page-level filter affordance."
|
|
443
|
+
@synonyms "filter bar, faceted filters, field filters, dropdowns"
|
|
444
|
+
@tier "presentation"
|
|
445
|
+
filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
|
|
446
|
+
@label "Filter bar look"
|
|
447
|
+
@description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
|
|
448
|
+
@synonyms "filter bar style, facet style, chips, pills"
|
|
449
|
+
@tier "presentation"
|
|
450
|
+
pageSize : number = 20
|
|
451
|
+
@label "How many candidates per page when paging?"
|
|
452
|
+
@description "Records fetched from the server per REFETCH_PAGE request. The initial load and search/filter refetches remain unpaginated (load every candidate) unless a consumer wires REFETCH_PAGE, so the default pick-list surface is unchanged. Safe to page: this atom's selection is a single transient id (never an accumulated array), so no already-picked item can be stranded on another page."
|
|
453
|
+
@synonyms "fetch limit, records per page, page limit"
|
|
454
|
+
@tier "presentation"
|
|
455
|
+
displayPageSize : number = 20
|
|
456
|
+
@label "How many candidates to show at once?"
|
|
457
|
+
@description "Rows visible per page before the data-grid's own pagination control appears — 20 matches the pick-list density sweet spot (Miller's law, 10-25 rows)."
|
|
458
|
+
@synonyms "visible rows, items per page, display limit, show per page"
|
|
459
|
+
@tier "presentation"
|
|
279
460
|
}
|
|
280
461
|
}
|
|
281
462
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
app std-tree "1.0.0"
|
|
2
|
-
"std-tree — generic hierarchical node browser. Fetches a flat TreeNode collection and renders it through file-tree's flat `items` mode (parentId-nested at render time by the component, not the .lolo); OPEN_NODE is the external handoff for a selected node's id. Rebindable: pass `fields` at the call site to retarget it at any parent-pointer entity (categories, org units, comment threads)."
|
|
2
|
+
"std-tree — generic hierarchical node browser. Fetches a flat TreeNode collection and renders it through file-tree's flat `items` mode (parentId-nested at render time by the component, not the .lolo); OPEN_NODE is the external handoff for a selected node's id. Rebindable: pass `fields` at the call site to retarget it at any parent-pointer entity (categories, org units, comment threads). Search/filter are wired (REFETCH_QUERY/REFETCH_FILTER re-fetch the flat node set server-side); pagination is deliberately NOT offered — a hierarchy is not a paged list, and paging a flat node set would sever children from parents that fell on another page."
|
|
3
|
+
type FilterSpec = { field : string!, label : string, options : [string] }
|
|
3
4
|
orbital TreeNodeOrbital {
|
|
4
5
|
type TreeNodeLoaded = Event { data : [TreeNode] } "Fired when the tree node collection finishes loading"
|
|
5
6
|
|
|
@@ -51,7 +52,37 @@ orbital TreeNodeOrbital {
|
|
|
51
52
|
direction: horizontal
|
|
52
53
|
gap: sm
|
|
53
54
|
type: stack
|
|
54
|
-
}, { type: divider }, {
|
|
55
|
+
}, { type: divider }, (if @config.bodySearch {
|
|
56
|
+
children: [{
|
|
57
|
+
className: "w-full max-w-md"
|
|
58
|
+
clearable: true
|
|
59
|
+
event: REFETCH_QUERY
|
|
60
|
+
placeholder: @config.searchPlaceholder
|
|
61
|
+
type: search-input
|
|
62
|
+
}]
|
|
63
|
+
direction: horizontal
|
|
64
|
+
gap: sm
|
|
65
|
+
type: stack
|
|
66
|
+
} {
|
|
67
|
+
children: []
|
|
68
|
+
gap: none
|
|
69
|
+
type: stack
|
|
70
|
+
}), (if [">", (array/len @config.filters), 0] {
|
|
71
|
+
children: [{
|
|
72
|
+
entity: TreeNode
|
|
73
|
+
event: REFETCH_FILTER
|
|
74
|
+
filters: @config.filters
|
|
75
|
+
look: @config.filterBarLook
|
|
76
|
+
type: filter-group
|
|
77
|
+
}]
|
|
78
|
+
direction: horizontal
|
|
79
|
+
gap: sm
|
|
80
|
+
type: stack
|
|
81
|
+
} {
|
|
82
|
+
children: []
|
|
83
|
+
gap: none
|
|
84
|
+
type: stack
|
|
85
|
+
}), {
|
|
55
86
|
items: (array/map ?data (fn row {
|
|
56
87
|
icon: @row.icon
|
|
57
88
|
id: @row.id
|
|
@@ -113,6 +144,22 @@ orbital TreeNodeOrbital {
|
|
|
113
144
|
gap: md
|
|
114
145
|
type: stack
|
|
115
146
|
})
|
|
147
|
+
;; REFETCH_* stays in `browsing` and DOES NOT replace the tree with a
|
|
148
|
+
;; spinner — search-as-you-type would otherwise unmount file-tree every
|
|
149
|
+
;; keystroke and flicker. The fetch fires in the background; when
|
|
150
|
+
;; TreeNodeLoaded arrives (handler below) React reconciles the tree in
|
|
151
|
+
;; place. No REFETCH_PAGE: a hierarchy is not a paged list — paging the
|
|
152
|
+
;; flat node set would sever children from parents on another page.
|
|
153
|
+
REFETCH_QUERY -> browsing
|
|
154
|
+
(fetch TreeNode {
|
|
155
|
+
emit: { failure: TreeNodeLoadFailed, success: TreeNodeLoaded }
|
|
156
|
+
filter: (or ["=", ?searchTerm, ""] (str/includes (object/get @entity name) ?searchTerm))
|
|
157
|
+
})
|
|
158
|
+
REFETCH_FILTER -> browsing
|
|
159
|
+
(fetch TreeNode {
|
|
160
|
+
emit: { failure: TreeNodeLoadFailed, success: TreeNodeLoaded }
|
|
161
|
+
filter: (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value])
|
|
162
|
+
})
|
|
116
163
|
OPEN_NODE -> browsing
|
|
117
164
|
(set @entity.id ?id)
|
|
118
165
|
TreeNodeLoaded -> browsing
|
|
@@ -132,7 +179,37 @@ orbital TreeNodeOrbital {
|
|
|
132
179
|
direction: horizontal
|
|
133
180
|
gap: sm
|
|
134
181
|
type: stack
|
|
135
|
-
}, { type: divider }, {
|
|
182
|
+
}, { type: divider }, (if @config.bodySearch {
|
|
183
|
+
children: [{
|
|
184
|
+
className: "w-full max-w-md"
|
|
185
|
+
clearable: true
|
|
186
|
+
event: REFETCH_QUERY
|
|
187
|
+
placeholder: @config.searchPlaceholder
|
|
188
|
+
type: search-input
|
|
189
|
+
}]
|
|
190
|
+
direction: horizontal
|
|
191
|
+
gap: sm
|
|
192
|
+
type: stack
|
|
193
|
+
} {
|
|
194
|
+
children: []
|
|
195
|
+
gap: none
|
|
196
|
+
type: stack
|
|
197
|
+
}), (if [">", (array/len @config.filters), 0] {
|
|
198
|
+
children: [{
|
|
199
|
+
entity: TreeNode
|
|
200
|
+
event: REFETCH_FILTER
|
|
201
|
+
filters: @config.filters
|
|
202
|
+
look: @config.filterBarLook
|
|
203
|
+
type: filter-group
|
|
204
|
+
}]
|
|
205
|
+
direction: horizontal
|
|
206
|
+
gap: sm
|
|
207
|
+
type: stack
|
|
208
|
+
} {
|
|
209
|
+
children: []
|
|
210
|
+
gap: none
|
|
211
|
+
type: stack
|
|
212
|
+
}), {
|
|
136
213
|
items: (array/map ?data (fn row {
|
|
137
214
|
icon: @row.icon
|
|
138
215
|
id: @row.id
|
|
@@ -214,6 +291,19 @@ orbital TreeNodeOrbital {
|
|
|
214
291
|
}
|
|
215
292
|
|
|
216
293
|
listens {
|
|
294
|
+
REFETCH_QUERY {
|
|
295
|
+
searchTerm : string!
|
|
296
|
+
}
|
|
297
|
+
@description "Triggers a tree refresh based on the current search query."
|
|
298
|
+
@synonyms "refresh, reload, update, re-query"
|
|
299
|
+
@tier "domain"
|
|
300
|
+
REFETCH_FILTER {
|
|
301
|
+
field : string!
|
|
302
|
+
value : string!
|
|
303
|
+
}
|
|
304
|
+
@description "Triggers a tree refresh based on current filter criteria."
|
|
305
|
+
@synonyms "refresh, filter, update, reload"
|
|
306
|
+
@tier "domain"
|
|
217
307
|
OPEN_NODE {
|
|
218
308
|
id : string!
|
|
219
309
|
}
|
|
@@ -240,6 +330,26 @@ orbital TreeNodeOrbital {
|
|
|
240
330
|
@label "Section title"
|
|
241
331
|
@description "Heading shown above the tree"
|
|
242
332
|
@tier "presentation"
|
|
333
|
+
bodySearch : boolean = false
|
|
334
|
+
@label "Show the tree's own search box?"
|
|
335
|
+
@description "Embeds a search box above the tree that re-fetches the flat node set server-side. Set false (the default) when the page already provides a search affordance (a toolbar Search molecule), so the surface shows one search box instead of two."
|
|
336
|
+
@synonyms "inline search, list search, built-in search, show search, duplicate search"
|
|
337
|
+
@tier "presentation"
|
|
338
|
+
searchPlaceholder : string = "Search…"
|
|
339
|
+
@label "Search box placeholder text"
|
|
340
|
+
@description "Hint text inside the tree search box (e.g. 'Search nodes…')."
|
|
341
|
+
@synonyms "search hint, placeholder, search box text, search label"
|
|
342
|
+
@tier "presentation"
|
|
343
|
+
filters : [FilterSpec] = []
|
|
344
|
+
@label "Which filters should appear above the tree?"
|
|
345
|
+
@description "Dropdown filter facets by field, rendered as an embedded filter bar above the tree; each selection fires REFETCH_FILTER on this trait, re-fetching the flat node set. Empty (the default) = no filter bar, which is the right setting whenever the page already composes std-filter beside this tree and routes `XFilter.FILTER -> REFETCH_FILTER` — that is the wired owner and remains the primary path."
|
|
346
|
+
@synonyms "filter bar, faceted filters, field filters, dropdowns"
|
|
347
|
+
@tier "presentation"
|
|
348
|
+
filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
|
|
349
|
+
@label "Filter bar look"
|
|
350
|
+
@description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
|
|
351
|
+
@synonyms "filter bar style, facet style, chips, pills"
|
|
352
|
+
@tier "presentation"
|
|
243
353
|
}
|
|
244
354
|
}
|
|
245
355
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
app std-area-fill "1.0.0"
|
|
2
|
+
"Headless area-fill mechanic: the player paints area under a curve by moving through a fill region. Listens BODY_MOVED and, per fill, accumulates height(x) × |dx| while the player is inside the fill's x-range (the per-tick step is clamped to 0.5 world units — the physical per-tick maximum — so teleports and respawns paint nothing) — height is linear interpolation of the config curve samples above the fill's baseline (the platformer body's leftSamp/rightSamp idiom). rateMode picks the rate: \"area\" (height × |dx|, default — fills fastest where the curve is tallest), \"speed\" (|dx| only, flat rate), or \"ride\" (|dx| only while the move reports skating — walking the same distance paints nothing). When a fill's filled reaches its target it flips reported and emits AREA_FILLED once (the shared entity carries the rest); each fill also stamps a frontier (x0 + span × min(1, filled/target)) so boards can shade the painted region left-to-right as the gauge grows. RESTART resets every fill. Renders nothing; boards shade the painted region and read filled/frontier from the shared entity (the render authority)."
|
|
3
|
+
orbital AreaFillOrbital {
|
|
4
|
+
type AreaFillSample = {
|
|
5
|
+
x : number!
|
|
6
|
+
y : number!
|
|
7
|
+
}
|
|
8
|
+
type AreaFill = {
|
|
9
|
+
id : string!
|
|
10
|
+
x0 : number!
|
|
11
|
+
x1 : number!
|
|
12
|
+
baseline : number!
|
|
13
|
+
target : number!
|
|
14
|
+
rateMode : string
|
|
15
|
+
hint : string
|
|
16
|
+
samples : [AreaFillSample]!
|
|
17
|
+
filled : number
|
|
18
|
+
frontier : number
|
|
19
|
+
reported : boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
entity AreaFillData [runtime, shared] {
|
|
23
|
+
id : string!
|
|
24
|
+
fills : [AreaFill] = []
|
|
25
|
+
lastX : number = 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
trait AreaFill -> @rebindable AreaFillData [lifecycle] {
|
|
29
|
+
initial: active
|
|
30
|
+
|
|
31
|
+
state active {
|
|
32
|
+
INIT -> active
|
|
33
|
+
(set @entity.fills (array/map @config.fills (fn f (object/merge @f { filled: 0, frontier: (object/get @f x0), reported: false }))))
|
|
34
|
+
(set @entity.lastX 0)
|
|
35
|
+
|
|
36
|
+
BODY_MOVED -> active when @config.running
|
|
37
|
+
(let ((adx (math/abs (- @payload.x @entity.lastX))))
|
|
38
|
+
(set @entity.fills (array/map @entity.fills (fn f
|
|
39
|
+
(let ((inside (and (>= @payload.x (object/get @f x0)) (<= @payload.x (object/get @f x1)))))
|
|
40
|
+
(let ((leftSamp (array/last (array/filter (object/get @f samples) (fn s (<= (object/get @s x) @payload.x))))))
|
|
41
|
+
(let ((rightSamp (array/first (array/filter (object/get @f samples) (fn s (> (object/get @s x) @payload.x))))))
|
|
42
|
+
(let ((h (if (or (== @leftSamp null) (== @rightSamp null)) 0
|
|
43
|
+
(- (+ (object/get @leftSamp y) (* (/ (- (object/get @rightSamp y) (object/get @leftSamp y)) (- (object/get @rightSamp x) (object/get @leftSamp x))) (- @payload.x (object/get @leftSamp x)))) (object/get @f baseline)))))
|
|
44
|
+
(let ((step (math/min @adx 0.5)))
|
|
45
|
+
(let ((rate (if @inside
|
|
46
|
+
(if (== (object/get @f rateMode) "ride") (if @payload.skating @step 0)
|
|
47
|
+
(if (== (object/get @f rateMode) "speed") @step
|
|
48
|
+
(* @step @h)))
|
|
49
|
+
0)))
|
|
50
|
+
(let ((newFilled (+ (object/get @f filled) @rate)))
|
|
51
|
+
(object/merge @f { filled: @newFilled, frontier: (+ (object/get @f x0) (* (- (object/get @f x1) (object/get @f x0)) (math/min 1 (/ @newFilled (object/get @f target))))) }))))))))))))
|
|
52
|
+
(set @entity.lastX @payload.x)
|
|
53
|
+
(let ((newly (array/filter @entity.fills (fn f (and (>= (object/get @f filled) (object/get @f target)) (== (object/get @f reported) false))))))
|
|
54
|
+
(if (> (array/len @newly) 0)
|
|
55
|
+
(do
|
|
56
|
+
(set @entity.fills (array/map @entity.fills (fn f (if (== (object/get @f id) (object/get (array/first @newly) id)) (object/merge @f { reported: true }) @f))))
|
|
57
|
+
(emit AREA_FILLED { id: (object/get (array/first @newly) id), x: @payload.x, y: @payload.y }))
|
|
58
|
+
null))
|
|
59
|
+
|
|
60
|
+
RESTART -> active
|
|
61
|
+
(set @entity.fills (array/map @config.fills (fn f (object/merge @f { filled: 0, frontier: (object/get @f x0), reported: false }))))
|
|
62
|
+
(set @entity.lastX 0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
emits {
|
|
66
|
+
AREA_FILLED -> external {
|
|
67
|
+
id : string!
|
|
68
|
+
x : number!
|
|
69
|
+
y : number!
|
|
70
|
+
}
|
|
71
|
+
@description "A fill's painted area reached its target (x, y = the player's position at completion, for burst placement). When several fills complete on one move, the first reports and the shared entity carries the rest."
|
|
72
|
+
@tier "domain"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
listens {
|
|
76
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
77
|
+
RESTART { id : string }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
config {
|
|
81
|
+
running : boolean = true
|
|
82
|
+
@label "Running"
|
|
83
|
+
@description "When true, BODY_MOVED paints every fill the player moves through; toggle to freeze every fill as-is."
|
|
84
|
+
@tier "domain"
|
|
85
|
+
fills : [AreaFill] = []
|
|
86
|
+
@label "Fills"
|
|
87
|
+
@description "One row per fill region: id, x-range (x0..x1 in world units), baseline the height is measured from, target area, rateMode (area | speed | ride — default area), optional hint, and the curve samples to interpolate. filled/frontier/reported are stamped at runtime."
|
|
88
|
+
@tier "domain"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
page "/area-fill" as AreaFillPage -> AreaFill
|
|
93
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
app std-loop-lock "1.0.0"
|
|
2
|
+
"Headless winding-lock mechanic: locks that open only after the player travels a full revolution around their anchor. Listens BODY_MOVED and, while the player stays within a radial band of a lock's ring, accumulates the wrapped angular delta of their position into swept (radians, signed — either direction counts); when |swept| reaches turns × 2π the lock flips unlocked and emits LOOP_UNLOCKED once. Entering the band reseeds the angle so leaving and returning never jumps the sweep; progress is kept across exits, so a revolution can be completed in stages. RESTART re-locks every lock. Renders nothing; boards draw the ring/chest and read swept from the shared entity (the render authority)."
|
|
3
|
+
orbital LoopLockOrbital {
|
|
4
|
+
type LoopLock = {
|
|
5
|
+
id : string!
|
|
6
|
+
x : number!
|
|
7
|
+
y : number!
|
|
8
|
+
radius : number!
|
|
9
|
+
band : number!
|
|
10
|
+
turns : number!
|
|
11
|
+
swept : number
|
|
12
|
+
lastAngle : number
|
|
13
|
+
inside : boolean
|
|
14
|
+
unlocked : boolean
|
|
15
|
+
reported : boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
entity LoopLockData [runtime, shared] {
|
|
19
|
+
id : string!
|
|
20
|
+
locks : [LoopLock] = []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
trait LoopLock -> @rebindable LoopLockData [lifecycle] {
|
|
24
|
+
initial: active
|
|
25
|
+
|
|
26
|
+
state active {
|
|
27
|
+
INIT -> active
|
|
28
|
+
(set @entity.locks (array/map @config.locks (fn l (object/merge @l { swept: 0, lastAngle: 0, inside: false, unlocked: false, reported: false }))))
|
|
29
|
+
|
|
30
|
+
BODY_MOVED -> active when @config.running
|
|
31
|
+
(set @entity.locks (array/map @entity.locks (fn l
|
|
32
|
+
(let ((dx (- @payload.x (object/get @l x))) (dy (- @payload.y (object/get @l y))))
|
|
33
|
+
(let ((dist (math/hypot @dx @dy)) (ang (math/atan2 @dy @dx)))
|
|
34
|
+
(let ((in (<= (math/abs (- @dist (object/get @l radius))) (object/get @l band))))
|
|
35
|
+
(let ((d (if (and @in (== (object/get @l inside) true))
|
|
36
|
+
(math/atan2 (math/sin (- @ang (object/get @l lastAngle))) (math/cos (- @ang (object/get @l lastAngle))))
|
|
37
|
+
0)))
|
|
38
|
+
(let ((swept (+ (object/get @l swept) @d)))
|
|
39
|
+
(object/merge @l { inside: @in, lastAngle: (if @in @ang (object/get @l lastAngle)), swept: @swept, unlocked: (or (object/get @l unlocked) (>= (math/abs @swept) (* (object/get @l turns) 6.283185307179586))) })))))))))
|
|
40
|
+
(let ((newly (array/filter @entity.locks (fn l (and (== (object/get @l unlocked) true) (== (object/get @l reported) false))))))
|
|
41
|
+
(if (> (array/len @newly) 0)
|
|
42
|
+
(do
|
|
43
|
+
(set @entity.locks (array/map @entity.locks (fn l (if (== (object/get @l id) (object/get (array/first @newly) id)) (object/merge @l { reported: true }) @l))))
|
|
44
|
+
(emit LOOP_UNLOCKED { id: (object/get (array/first @newly) id), x: (object/get (array/first @newly) x), y: (object/get (array/first @newly) y) }))
|
|
45
|
+
null))
|
|
46
|
+
|
|
47
|
+
RESTART -> active
|
|
48
|
+
(set @entity.locks (array/map @config.locks (fn l (object/merge @l { swept: 0, lastAngle: 0, inside: false, unlocked: false, reported: false }))))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
emits {
|
|
52
|
+
LOOP_UNLOCKED -> external {
|
|
53
|
+
id : string!
|
|
54
|
+
x : number!
|
|
55
|
+
y : number!
|
|
56
|
+
}
|
|
57
|
+
@description "A lock's angular sweep reached turns × 2π and it flipped unlocked; boards layer treasure fx and rewards. When several locks open on one move, the first reports and the shared entity carries the rest."
|
|
58
|
+
@tier "domain"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
listens {
|
|
62
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
63
|
+
RESTART { id : string }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
config {
|
|
67
|
+
running : boolean = true
|
|
68
|
+
@label "Running"
|
|
69
|
+
@description "When true, BODY_MOVED accumulates angular sweep and unlocks locks; toggle to freeze every lock as-is."
|
|
70
|
+
@tier "domain"
|
|
71
|
+
locks : [LoopLock] = []
|
|
72
|
+
@label "Locks"
|
|
73
|
+
@description "One row per winding lock: id, anchor (x, y in world units), ring radius, radial tolerance band, and revolutions required. swept/inside/unlocked are stamped at runtime."
|
|
74
|
+
@tier "domain"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
page "/loop-lock" as LoopLockPage -> LoopLock
|
|
79
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
app std-pad-lock "1.0.0"
|
|
2
|
+
"Headless pad-lock mechanic: floor pads that light when the player steps on them, completing a set when every pad is lit. Listens BODY_MOVED and lights any unlit live pad the player's rect touches (|dx| <= reachX, |dy| <= reachY of the pad center); a lit pad stays lit. Pads may carry a group number (grp 0 = ungrouped, always live): when groups are used, only pads of the lowest uncompleted group are live — pads of later groups refuse to light until the current group finishes, so one sum must be finished before the next. When a group completes the trait emits PAD_GROUP_COMPLETED once for it (first newly completed group reports; the shared entity carries the rest); when every pad in the set is lit it stamps completed and emits PAD_SET_COMPLETED once. RESTART unlights every pad and resets the set. Renders nothing; boards draw the pads and read lit from the shared entity (the render authority)."
|
|
3
|
+
orbital PadLockOrbital {
|
|
4
|
+
type PadLock = {
|
|
5
|
+
id : string!
|
|
6
|
+
x : number!
|
|
7
|
+
y : number!
|
|
8
|
+
grp : number!
|
|
9
|
+
lit : boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
entity PadLockData [runtime, shared] {
|
|
13
|
+
id : string!
|
|
14
|
+
pads : [PadLock] = []
|
|
15
|
+
groupsDone : [number] = []
|
|
16
|
+
completed : boolean = false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
trait PadLock -> @rebindable PadLockData [lifecycle] {
|
|
20
|
+
initial: active
|
|
21
|
+
|
|
22
|
+
state active {
|
|
23
|
+
INIT -> active
|
|
24
|
+
(set @entity.pads (array/map @config.pads (fn p (object/merge @p { lit: false }))))
|
|
25
|
+
(set @entity.groupsDone [])
|
|
26
|
+
(set @entity.completed false)
|
|
27
|
+
|
|
28
|
+
BODY_MOVED -> active when @config.running
|
|
29
|
+
(let ((grpIds (array/reduce (array/map (array/filter @entity.pads (fn p (> (object/get @p grp) 0))) (fn p (object/get @p grp))) [] (fn acc g (if (> (array/len (array/filter @acc (fn x (== @x @g)))) 0) @acc (array/append @acc @g))))))
|
|
30
|
+
(let ((openGrps (array/filter @grpIds (fn g (< (array/len (array/filter @entity.pads (fn p (and (== (object/get @p grp) @g) (== (object/get @p lit) true))))) (array/len (array/filter @entity.pads (fn p (== (object/get @p grp) @g)))))))))
|
|
31
|
+
(let ((currentGrp (array/reduce @openGrps 9999 (fn acc g (math/min @acc @g)))))
|
|
32
|
+
(let ((newPads (array/map @entity.pads (fn p
|
|
33
|
+
(object/merge @p { lit: (or (object/get @p lit) (and (or (== (object/get @p grp) 0) (== (object/get @p grp) @currentGrp)) (and (<= (math/abs (- @payload.x (object/get @p x))) @config.reachX) (<= (math/abs (- @payload.y (object/get @p y))) @config.reachY)))) })))))
|
|
34
|
+
(let ((doneNow (array/filter @grpIds (fn g (== (array/len (array/filter @newPads (fn p (and (== (object/get @p grp) @g) (== (object/get @p lit) true))))) (array/len (array/filter @newPads (fn p (== (object/get @p grp) @g)))))))))
|
|
35
|
+
(let ((newlyGrp (array/filter @doneNow (fn g (== (array/len (array/filter @entity.groupsDone (fn d (== @d @g)))) 0)))))
|
|
36
|
+
(do
|
|
37
|
+
(set @entity.pads @newPads)
|
|
38
|
+
(if (> (array/len @newlyGrp) 0)
|
|
39
|
+
(do
|
|
40
|
+
(set @entity.groupsDone @doneNow)
|
|
41
|
+
(emit PAD_GROUP_COMPLETED { grp: (array/first @newlyGrp) }))
|
|
42
|
+
null)
|
|
43
|
+
(if (and (== @entity.completed false) (== (array/len (array/filter @newPads (fn p (== (object/get @p lit) false)))) 0))
|
|
44
|
+
(do
|
|
45
|
+
(set @entity.completed true)
|
|
46
|
+
(emit PAD_SET_COMPLETED {}))
|
|
47
|
+
null))))))))
|
|
48
|
+
|
|
49
|
+
RESTART -> active
|
|
50
|
+
(set @entity.pads (array/map @config.pads (fn p (object/merge @p { lit: false }))))
|
|
51
|
+
(set @entity.groupsDone [])
|
|
52
|
+
(set @entity.completed false)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
emits {
|
|
56
|
+
PAD_GROUP_COMPLETED -> external {
|
|
57
|
+
grp : number!
|
|
58
|
+
}
|
|
59
|
+
@description "Every pad of one group is lit; fires once per group, in group order — later groups stay dead until the lowest uncompleted group finishes. Boards layer a small fx and unlock the next group's pads."
|
|
60
|
+
@tier "domain"
|
|
61
|
+
PAD_SET_COMPLETED -> external {
|
|
62
|
+
}
|
|
63
|
+
@description "Every pad in the set is lit; fires once per run. Boards layer the treasure fx and spill the bonus reward."
|
|
64
|
+
@tier "domain"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
listens {
|
|
68
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
69
|
+
RESTART { id : string }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
config {
|
|
73
|
+
running : boolean = true
|
|
74
|
+
@label "Running"
|
|
75
|
+
@description "When true, BODY_MOVED lights touched live pads and reports group/set completion; toggle to freeze every pad as-is."
|
|
76
|
+
@tier "domain"
|
|
77
|
+
pads : [PadLock] = []
|
|
78
|
+
@label "Pads"
|
|
79
|
+
@description "One row per floor pad: id, center (x, y in world units), and grp (0 = ungrouped, always live; N = live only while it is the lowest uncompleted group). lit is stamped at runtime."
|
|
80
|
+
@tier "domain"
|
|
81
|
+
reachX : number = 0.7
|
|
82
|
+
@label "Reach X"
|
|
83
|
+
@description "Horizontal tolerance (world units) around a pad's center when testing player contact (approximates the body's half-width plus a step)."
|
|
84
|
+
@tier "domain"
|
|
85
|
+
reachY : number = 1.2
|
|
86
|
+
@label "Reach Y"
|
|
87
|
+
@description "Vertical tolerance (world units) around a pad's center when testing player contact (covers skating over the surface the pad sits on)."
|
|
88
|
+
@tier "domain"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
page "/pad-lock" as PadLockPage -> PadLock
|
|
93
|
+
}
|