@almadar/std 16.217.0 → 16.219.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/infra/atoms/std-data-import.lolo +56 -5
- package/behaviors/lolo/infra/atoms/std-notification-center.lolo +18 -18
- package/behaviors/lolo/infra/atoms/std-notification-preferences.lolo +5 -5
- package/behaviors/lolo/infra/atoms/std-notify-on-event.lolo +17 -18
- package/behaviors/lolo/ui/core/atoms/std-gantt-schedule.lolo +215 -0
- package/behaviors/lolo/ui/core/atoms/ui-textarea.lolo +2 -2
- package/behaviors/lolo/ui/core/molecules/ui-gantt.lolo +169 -0
- package/behaviors/lolo/ui/game/atoms/ui-canvas-2d.lolo +2 -2
- package/behaviors/lolo/ui/game/atoms/ui-canvas.lolo +2 -2
- package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +2 -2
- package/behaviors/registry/infra/atoms/std-data-import.orb +329 -6
- package/behaviors/registry/infra/atoms/std-notification-center.orb +41 -33
- package/behaviors/registry/infra/atoms/std-notification-preferences.orb +10 -17
- package/behaviors/registry/infra/atoms/std-notify-on-event.orb +198 -195
- package/behaviors/registry/ui/core/atoms/std-gantt-schedule.orb +723 -0
- package/behaviors/registry/ui/core/molecules/ui-gantt.orb +508 -0
- package/canonical-operators.json +46 -1
- package/dist/behaviors/exports-reader.js +4455 -4608
- package/dist/behaviors/functions/index.d.ts +270 -47
- package/dist/behaviors/functions/index.js +4520 -4682
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +4531 -4684
- package/dist/behaviors/query.js +4455 -4608
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +23 -11
- package/dist/behaviors-registry.json +2347 -666
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4580 -4697
- package/dist/knob-embeddings.hash.json +3 -3
- package/dist/knob-embeddings.json +1 -1
- package/dist/modules/anim.d.ts +1 -1
- package/dist/modules/array.d.ts +1 -1
- package/dist/modules/async.d.ts +1 -1
- package/dist/modules/behavior.d.ts +1 -1
- package/dist/modules/browser.d.ts +1 -1
- package/dist/modules/composition.d.ts +1 -1
- package/dist/modules/contract.d.ts +1 -1
- package/dist/modules/core.d.ts +1 -1
- package/dist/modules/data.d.ts +1 -1
- package/dist/modules/ease.d.ts +1 -1
- package/dist/modules/format.d.ts +1 -1
- package/dist/modules/geo.d.ts +1 -1
- package/dist/modules/graph.d.ts +1 -1
- package/dist/modules/grid.d.ts +1 -1
- package/dist/modules/index.d.ts +2 -1
- package/dist/modules/index.js +30 -1
- package/dist/modules/integration.d.ts +1 -1
- package/dist/modules/json.d.ts +20 -0
- package/dist/modules/json.js +30 -0
- package/dist/modules/llm.d.ts +1 -1
- package/dist/modules/math.d.ts +1 -1
- package/dist/modules/memory.d.ts +1 -1
- package/dist/modules/nn.d.ts +1 -1
- package/dist/modules/noise.d.ts +1 -1
- package/dist/modules/object.d.ts +1 -1
- package/dist/modules/os.d.ts +1 -1
- package/dist/modules/path.d.ts +1 -1
- package/dist/modules/prob.d.ts +1 -1
- package/dist/modules/session.d.ts +1 -1
- package/dist/modules/str.d.ts +1 -1
- package/dist/modules/tensor.d.ts +1 -1
- package/dist/modules/time.d.ts +1 -1
- package/dist/modules/trace.d.ts +1 -1
- package/dist/modules/train.d.ts +1 -1
- package/dist/modules/validate.d.ts +1 -1
- package/dist/modules/vector.d.ts +1 -1
- package/dist/modules/workspace.d.ts +1 -1
- package/dist/registry/factory-signatures.json +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +28 -0
- package/dist/{types-C-Yerb4-.d.ts → types-DpbKBJu8.d.ts} +3 -3
- package/i18n/ar.json +3 -1
- package/i18n/en.json +2 -0
- package/i18n/sl.json +3 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
app std-data-import "1.0.0"
|
|
2
|
-
"std-data-import — staged CSV import run, source-agnostic: upload → staged review → confirm → handoff. UPLOAD_CSV opens a run and persists an ImportBatch row (status uploaded, fieldMappingJson recorded verbatim for provenance); the host's file UI parses the CSV and answers with STAGE_ROWS, one JSON string per raw row, which the atom bulk-persists into import_rows against the batch id and presents as a staged review (row/error counts + Confirm/Discard). CONFIRM_IMPORT marks the batch confirmed
|
|
2
|
+
"std-data-import — staged CSV import run, source-agnostic: upload → staged review → confirm → parse+map → handoff. UPLOAD_CSV opens a run and persists an ImportBatch row (status uploaded, fieldMappingJson recorded verbatim for provenance); the host's file UI parses the CSV and answers with STAGE_ROWS, one JSON string per raw row, which the atom bulk-persists into import_rows against the batch id and presents as a staged review (row/error counts + Confirm/Discard). CONFIRM_IMPORT marks the batch confirmed, emits IMPORT_CONFIRMED { batchId, sourceName, rowCount }, and fetches the batch's staged rows; on fetch success the atom parses each rowJson via json/parse, remaps keys through the batch's mappingJson, and emits IMPORT_ROWS_PARSED { batchId, rows, failed } — the persist half is the CONSUMER organism's: it listens for IMPORT_ROWS_PARSED and persists the mapped rows into its own target entity (cross-atom persist into a consumer's entity is not expressible from here). This is deliberately the CSV-file path: the atom never calls a source service — sourceName/fieldMappingJson/targetEntity/historyWindowDays are declarative consumer configs, and any persist failure marks the batch row failed and fires IMPORT_FAILED, never a silent swallow. Compose with std-import for the file-picker menu."
|
|
3
3
|
orbital DataImportOrbital {
|
|
4
4
|
|
|
5
5
|
entity ImportBatch [persistent: import_batches] {
|
|
@@ -8,7 +8,7 @@ orbital DataImportOrbital {
|
|
|
8
8
|
status : "uploaded" | "staged" | "confirmed" | "failed" @description "Lifecycle position of the import run." = uploaded
|
|
9
9
|
rowCount : number @description "Number of rows staged for review." @synonyms "rows, staged count, record count" = 0
|
|
10
10
|
errorCount : number @description "Number of rows the host parser flagged with errors during staging." @synonyms "errors, failed rows, parse errors" = 0
|
|
11
|
-
mappingJson : string @description "Source-column → target-field mapping (JSON string), recorded verbatim from config for provenance; the
|
|
11
|
+
mappingJson : string @description "Source-column → target-field mapping (JSON string), recorded verbatim from config for provenance; the atom applies it when parsing staged rows on confirm." @synonyms "field mapping, column mapping, mapping" = "{}"
|
|
12
12
|
createdAt : number @description "Run start timestamp." = 0
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -145,6 +145,9 @@ orbital DataImportOrbital {
|
|
|
145
145
|
variant: success
|
|
146
146
|
})
|
|
147
147
|
(emit IMPORT_CONFIRMED { batchId: @entity.id, rowCount: @entity.rowCount, sourceName: @entity.sourceName })
|
|
148
|
+
(fetch ImportRow {
|
|
149
|
+
emit: { failure: IMPORT_PERSIST_FAILED, success: IMPORT_ROWS_FETCHED }
|
|
150
|
+
})
|
|
148
151
|
;; Same transient-failure self-loop as in `uploaded`.
|
|
149
152
|
IMPORT_PERSIST_FAILED -> staged
|
|
150
153
|
(set @entity.status failed)
|
|
@@ -166,6 +169,40 @@ orbital DataImportOrbital {
|
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
state confirmed {
|
|
172
|
+
;; The parse half: fetch success delivers every staged row flat (?data);
|
|
173
|
+
;; filter to this batch, keep rows whose rowJson parses to an object,
|
|
174
|
+
;; remap keys through the batch's mappingJson, and hand the consumer
|
|
175
|
+
;; mapped rows + a failed count (failed rows surface, never drop
|
|
176
|
+
;; silently). json/parse yields null on invalid JSON, so the guard-level
|
|
177
|
+
;; branch is validate/object on the parse result.
|
|
178
|
+
IMPORT_ROWS_FETCHED -> confirmed
|
|
179
|
+
(emit IMPORT_ROWS_PARSED {
|
|
180
|
+
batchId: @entity.id
|
|
181
|
+
failed: (array/len (array/filter ?data (fn row (and
|
|
182
|
+
["=", (object/get @row "batchId"), @entity.id]
|
|
183
|
+
(not (validate/object (json/parse (object/get @row "rowJson"))))))))
|
|
184
|
+
rows: (array/map
|
|
185
|
+
(array/filter ?data (fn row (and
|
|
186
|
+
["=", (object/get @row "batchId"), @entity.id]
|
|
187
|
+
(validate/object (json/parse (object/get @row "rowJson"))))))
|
|
188
|
+
(fn row (object/mapKeys
|
|
189
|
+
(json/parse (object/get @row "rowJson"))
|
|
190
|
+
(fn k (if (object/has (json/parse @entity.mappingJson) @k)
|
|
191
|
+
(object/get (json/parse @entity.mappingJson) @k)
|
|
192
|
+
@k)))))
|
|
193
|
+
})
|
|
194
|
+
;; Same transient-failure self-loop as in `staged` — the row fetch can
|
|
195
|
+
;; fail after the batch row was already persisted as confirmed.
|
|
196
|
+
IMPORT_PERSIST_FAILED -> confirmed
|
|
197
|
+
(set @entity.status failed)
|
|
198
|
+
(set @entity.errorCount 1)
|
|
199
|
+
(persist update ImportBatch { errorCount: 1, id: @entity.id, status: failed })
|
|
200
|
+
(emit IMPORT_FAILED { batchId: @entity.id, error: ?error })
|
|
201
|
+
(render-ui main {
|
|
202
|
+
message: ?error
|
|
203
|
+
type: alert
|
|
204
|
+
variant: error
|
|
205
|
+
})
|
|
169
206
|
RESET -> idle
|
|
170
207
|
(set @entity.status uploaded)
|
|
171
208
|
(set @entity.rowCount 0)
|
|
@@ -174,6 +211,7 @@ orbital DataImportOrbital {
|
|
|
174
211
|
;; Self-loop so the validator sees an in-atom listener for the
|
|
175
212
|
;; externally-consumed confirmation event.
|
|
176
213
|
IMPORT_CONFIRMED -> confirmed
|
|
214
|
+
IMPORT_ROWS_PARSED -> confirmed
|
|
177
215
|
}
|
|
178
216
|
|
|
179
217
|
state failed {
|
|
@@ -206,9 +244,22 @@ orbital DataImportOrbital {
|
|
|
206
244
|
sourceName : string
|
|
207
245
|
rowCount : number
|
|
208
246
|
}
|
|
209
|
-
@description "The user confirmed the staged review and the batch row is persisted as confirmed.
|
|
247
|
+
@description "The user confirmed the staged review and the batch row is persisted as confirmed. The atom then fetches the batch's staged rows, parses and key-maps them, and emits IMPORT_ROWS_PARSED for the consumer's persist half."
|
|
210
248
|
@synonyms "import confirmed, commit import, accept staged rows, approve import"
|
|
211
249
|
@tier "domain"
|
|
250
|
+
IMPORT_ROWS_FETCHED {
|
|
251
|
+
data : [ImportRow]
|
|
252
|
+
}
|
|
253
|
+
@description "import_rows collection-fetch success on confirm — payload carries every staged row flat; the atom filters to @entity.id by batchId in the IMPORT_ROWS_PARSED arm (self-loop only)"
|
|
254
|
+
@tier "secondary"
|
|
255
|
+
IMPORT_ROWS_PARSED -> external {
|
|
256
|
+
batchId : string!
|
|
257
|
+
failed : number
|
|
258
|
+
rows : [Map string scalar]
|
|
259
|
+
}
|
|
260
|
+
@description "Confirmed rows parsed and key-mapped: each staged row's rowJson was parsed via json/parse and remapped through the batch's mappingJson; failed counts rows whose rowJson did not parse to an object (surfaced, never dropped). The consumer persists rows into its own target entity and marks them imported."
|
|
261
|
+
@synonyms "rows parsed, rows mapped, import rows ready"
|
|
262
|
+
@tier "domain"
|
|
212
263
|
IMPORT_FAILED -> external {
|
|
213
264
|
batchId : string
|
|
214
265
|
error : string!
|
|
@@ -233,7 +284,7 @@ orbital DataImportOrbital {
|
|
|
233
284
|
@description "Host-side CSV parsing finished: rows carries one JSON string per parsed row (raw source shape, pre-mapping); errorCount counts rows the parser flagged. The atom bulk-persists them into import_rows and opens the staged review."
|
|
234
285
|
@tier "internal"
|
|
235
286
|
CONFIRM_IMPORT
|
|
236
|
-
@description "The user approved the staged review; the batch is marked confirmed
|
|
287
|
+
@description "The user approved the staged review; the batch is marked confirmed, IMPORT_CONFIRMED fires, and the atom parses+maps the staged rows into IMPORT_ROWS_PARSED for the consumer's persist half."
|
|
237
288
|
@synonyms "confirm import, approve staged rows, accept import"
|
|
238
289
|
@tier "domain"
|
|
239
290
|
DISCARD_IMPORT
|
|
@@ -252,7 +303,7 @@ orbital DataImportOrbital {
|
|
|
252
303
|
@tier "domain"
|
|
253
304
|
fieldMappingJson : string = "{}"
|
|
254
305
|
@label "Field mapping (JSON)"
|
|
255
|
-
@description "JSON object mapping source CSV column names to target field names. Stored verbatim on the batch row (mappingJson)
|
|
306
|
+
@description "JSON object mapping source CSV column names to target field names. Stored verbatim on the batch row (mappingJson); the atom applies it when it parses staged rows into IMPORT_ROWS_PARSED, so the consumer persists already-mapped rows into the target entity."
|
|
256
307
|
@synonyms "field mapping, column mapping, map columns, remap fields, csv columns"
|
|
257
308
|
@tier "domain"
|
|
258
309
|
targetEntity : string = ""
|
|
@@ -9,12 +9,13 @@ orbital NotificationCenterOrbital {
|
|
|
9
9
|
|
|
10
10
|
entity NotificationPreference [persistent: notificationPreferences] {
|
|
11
11
|
id : string!
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
personId : string! @description "Owner of this preference row — rows are meant to be private to their owner. Access predicates need an [identity] roster, so the binding organism (which declares one) owns the @read/@update scoping; the generic atom cannot." @synonyms "user, owner, account"
|
|
13
|
+
eventKey : string @description "Event key this preference applies to (matches a listener's eventKey); blank = all events." @synonyms "event, trigger, subscription, topic" = ""
|
|
14
|
+
channel : "in_app" | "email" | "push" | "slack" | "sms" | "webhook" @description "Channel override for this user." @synonyms "preferred channel, delivery method" = in_app
|
|
14
15
|
mode : "instant" | "digest" | "off" @description "Delivery mode: instant (default), digest (deferred to the drain cycle), or off (muted)." @synonyms "delivery mode, frequency, cadence, mute, defer" = instant
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
quietHours : string @description "Per-user quiet-hours window as \"HH:MM-HH:MM\" (e.g. 22:00-07:00); blank = disabled." @synonyms "do not disturb, quiet window, night mode" = ""
|
|
17
|
+
createdAt : number @description "Timestamp indicating when this preference row was created." @synonyms "creation time, created" = 0
|
|
18
|
+
updatedAt : number @description "Timestamp indicating when this preference row was last updated." @synonyms "modified, last updated" = 0
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
entity PushSubscription [persistent: pushSubscriptions] {
|
|
@@ -126,20 +127,21 @@ orbital NotificationCenterOrbital {
|
|
|
126
127
|
SAVE_PREFERENCE -> idle
|
|
127
128
|
(persist create NotificationPreference {
|
|
128
129
|
channel: ?channel
|
|
129
|
-
|
|
130
|
+
createdAt: @now
|
|
131
|
+
eventKey: ?eventKey
|
|
130
132
|
mode: ?mode
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
personId: @user.id
|
|
134
|
+
quietHours: ?quietHours
|
|
135
|
+
updatedAt: @now
|
|
134
136
|
} { emit: { failure: PREFERENCE_SAVE_FAILED, success: PREFERENCE_SAVED } })
|
|
135
137
|
UPDATE_PREFERENCE -> idle
|
|
136
138
|
(persist update NotificationPreference {
|
|
137
139
|
id: ?id
|
|
138
140
|
channel: ?channel
|
|
139
|
-
|
|
141
|
+
eventKey: ?eventKey
|
|
140
142
|
mode: ?mode
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
quietHours: ?quietHours
|
|
144
|
+
updatedAt: @now
|
|
143
145
|
} { emit: { failure: PREFERENCE_SAVE_FAILED, success: PREFERENCE_SAVED } })
|
|
144
146
|
DELETE_PREFERENCE -> idle
|
|
145
147
|
(persist delete NotificationPreference ?id { emit: { failure: PREFERENCE_SAVE_FAILED, success: PREFERENCE_DELETED } })
|
|
@@ -171,19 +173,17 @@ orbital NotificationCenterOrbital {
|
|
|
171
173
|
|
|
172
174
|
listens {
|
|
173
175
|
SAVE_PREFERENCE {
|
|
174
|
-
|
|
176
|
+
eventKey : string
|
|
175
177
|
mode : string
|
|
176
178
|
channel : string
|
|
177
|
-
|
|
178
|
-
quietEndHour : number
|
|
179
|
+
quietHours : string
|
|
179
180
|
}
|
|
180
181
|
UPDATE_PREFERENCE {
|
|
181
182
|
id : string
|
|
182
|
-
|
|
183
|
+
eventKey : string
|
|
183
184
|
mode : string
|
|
184
185
|
channel : string
|
|
185
|
-
|
|
186
|
-
quietEndHour : number
|
|
186
|
+
quietHours : string
|
|
187
187
|
}
|
|
188
188
|
DELETE_PREFERENCE {
|
|
189
189
|
id : string
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
app std-notification-preferences "1.0.0"
|
|
2
|
-
"std-notification-preferences — per-person notification preference store plus a consultable gate. Owns NotificationPreference rows (one per person + event key: channel override in_app/email/push/slack, mode instant/digest, quiet-hours window 'HH:MM-HH:MM' such as 22:00-07:00). The PreferencesManage trait is standard CRUD over the store (list / create / update / delete, persisted, success/failure cascade arms): a data-grid browse plus ModalRecordModal create/edit forms whose SAVE carries the full row payload (std-list daisy-chain idiom). The PreferenceGate trait is the consult half other notify traits call: emit CHECK_PREFERENCE { personId, eventKey } and the gate fetches the matching row (a row whose eventKey is blank applies to every event) and answers PREFERENCE_RESOLVED { personId, eventKey, channel, mode, suppressed } — suppressed is true only when the row's quietHours window covers the current hour; mode 'digest' rides on the verdict for the caller to defer. Default policy: no matching row — or a failed lookup — resolves fail-open to { channel: in_app, mode: instant, suppressed: false }; the absence of a preference never silences a notification."
|
|
2
|
+
"std-notification-preferences — per-person notification preference store plus a consultable gate. Owns NotificationPreference rows (one per person + event key: channel override in_app/email/push/slack/sms/webhook, mode instant/digest/off, quiet-hours window 'HH:MM-HH:MM' such as 22:00-07:00). The PreferencesManage trait is standard CRUD over the store (list / create / update / delete, persisted, success/failure cascade arms): a data-grid browse plus ModalRecordModal create/edit forms whose SAVE carries the full row payload (std-list daisy-chain idiom). The PreferenceGate trait is the consult half other notify traits call: emit CHECK_PREFERENCE { personId, eventKey } and the gate fetches the matching row (a row whose eventKey is blank applies to every event) and answers PREFERENCE_RESOLVED { personId, eventKey, channel, mode, suppressed } — suppressed is true only when the row's quietHours window covers the current hour; mode 'digest' rides on the verdict for the caller to defer. Default policy: no matching row — or a failed lookup — resolves fail-open to { channel: in_app, mode: instant, suppressed: false }; the absence of a preference never silences a notification."
|
|
3
3
|
orbital NotificationPreferencesOrbital {
|
|
4
4
|
uses Modal from "std/behaviors/std-modal"
|
|
5
5
|
|
|
@@ -7,12 +7,12 @@ orbital NotificationPreferencesOrbital {
|
|
|
7
7
|
|
|
8
8
|
type PREFERENCE_RESOLVED = Event { personId : string, eventKey : string, channel : string, mode : string, suppressed : boolean } "The gate's verdict. Fail-open defaults: no matching row or a failed lookup resolves { channel: in_app, mode: instant, suppressed: false } — absence of a preference never silences a notification."
|
|
9
9
|
|
|
10
|
-
entity NotificationPreference [persistent:
|
|
10
|
+
entity NotificationPreference [persistent: notificationPreferences] {
|
|
11
11
|
id : string!
|
|
12
12
|
personId : string! @description "The person this preference row belongs to." @synonyms "user, recipient, owner, account"
|
|
13
|
-
eventKey : string
|
|
14
|
-
channel : "in_app" | "email" | "push" | "slack" @description "Delivery channel override for this person." @synonyms "preferred channel, delivery method" = in_app
|
|
15
|
-
mode : "instant" | "digest" @description "Delivery mode: instant (default)
|
|
13
|
+
eventKey : string @description "Event key this preference applies to (matches a notify listener's eventKey); blank = all events." @synonyms "event, trigger, topic, subscription" = ""
|
|
14
|
+
channel : "in_app" | "email" | "push" | "slack" | "sms" | "webhook" @description "Delivery channel override for this person." @synonyms "preferred channel, delivery method" = in_app
|
|
15
|
+
mode : "instant" | "digest" | "off" @description "Delivery mode: instant (default), digest (deferred to a batch cycle), or off (muted)." @synonyms "delivery mode, frequency, cadence, mute, defer" = instant
|
|
16
16
|
quietHours : string @description "Quiet-hours window as \"HH:MM-HH:MM\" (e.g. 22:00-07:00); blank or equal start/end = disabled." @synonyms "do not disturb, quiet window, night mode" = ""
|
|
17
17
|
createdAt : number @description "Timestamp indicating when this preference row was created." @synonyms "creation time, created" = 0
|
|
18
18
|
updatedAt : number @description "Timestamp indicating when this preference row was last updated." @synonyms "modified, last updated" = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
app std-notify-on-event "2.0.0"
|
|
2
|
-
"std-notify-on-event — cross-orbital wiring atom. Subscribes to events from another orbital (e.g. CheckoutOrbital emits OrderPlaced, OrderRecordOrbital reacts via this atom) and dispatches a notification. Use when one orbital needs to react to or be notified about events that happen in another orbital — the wiring goes through this atom's `EventOccurred` listener which the molecule renames via traitOverrides.events to the upstream event name. Dispatch gates on `guardOp`: `eq` (default) does string-equality on `triggerStatus` against the upstream event's `status` field; `gt`/`lt`/`gte`/`lte` instead numerically compare `guardField` (read from the event's `data` payload) against `guardValue`, for threshold-crossing asks such as low-stock alerts. Resolves recipient from `recipients` (first entry; multi-recipient fan-out is a future extension), falling back to `fallbackRecipient` when set, or `@user.id` otherwise. Skips dispatch when the recipient is in `suppressionList`. Delivery consults std-notification-center data before dispatching: per-
|
|
3
|
-
type PrefRow = {
|
|
2
|
+
"std-notify-on-event — cross-orbital wiring atom. Subscribes to events from another orbital (e.g. CheckoutOrbital emits OrderPlaced, OrderRecordOrbital reacts via this atom) and dispatches a notification. Use when one orbital needs to react to or be notified about events that happen in another orbital — the wiring goes through this atom's `EventOccurred` listener which the molecule renames via traitOverrides.events to the upstream event name. Dispatch gates on `guardOp`: `eq` (default) does string-equality on `triggerStatus` against the upstream event's `status` field; `gt`/`lt`/`gte`/`lte` instead numerically compare `guardField` (read from the event's `data` payload) against `guardValue`, for threshold-crossing asks such as low-stock alerts. Resolves recipient from `recipients` (first entry; multi-recipient fan-out is a future extension), falling back to `fallbackRecipient` when set, or `@user.id` otherwise. Skips dispatch when the recipient is in `suppressionList`. Delivery consults std-notification-center data before dispatching: per-person NotificationPreference rows (matched on `personId` = recipient and `eventKey` = this listener's `eventKey`, blank = all) can mute (`mode: off`), defer to the digest drain (`mode: digest`), override the channel, or impose per-user quiet hours (a 'HH:MM-HH:MM' window, blank = none); the atom-level `quietStartHour`/`quietEndHour` window and the `frequencyCapPerWeek` rolling cap (counted against dispatched NotificationRecord audit rows for the same recipient + channel) suppress likewise. Every suppression persists an honest audit row (`dispatched: false` with the reason in `error`: quiet-hours / digest-deferred / muted-by-preference / frequency-cap / no-push-subscription) which std-notification-center's digest cycle drains later. Channels: `in-app` emits the `notify` toast; `email`/`sms`/`webhook` dispatch through call-service (email.send / twilio.sendSMS / webhook.send); `push` looks up the recipient's PushSubscription row (owned by std-notification-center) and dispatches web-push via push.send (`pushTitle` falls back to `template`; `pushDeepLink`/`pushIcon` optional). The audit row's `dispatched` flag is honest — true only once the service call succeeds."
|
|
3
|
+
type PrefRow = { personId : string, eventKey : string, mode : string, channel : string, quietHours : string }
|
|
4
4
|
type AuditRow = { recipient : string, channel : string, dispatched : boolean, recordedAt : number }
|
|
5
5
|
type SubRow = { userId : string, endpoint : string, p256dh : string, auth : string }
|
|
6
6
|
orbital NotifyOnEventOrbital {
|
|
@@ -19,8 +19,7 @@ orbital NotifyOnEventOrbital {
|
|
|
19
19
|
recordedAt : number @description "Timestamp indicating when the notification record was created." @synonyms "creation time, timestamp, date, when" = 0
|
|
20
20
|
prefMode : string @description "Scratch: effective preference mode resolved for the current dispatch (instant/digest/off)." @synonyms "resolved mode" = ""
|
|
21
21
|
prefChannel : string @description "Scratch: channel override from the matched preference row; blank = no override." @synonyms "channel override" = ""
|
|
22
|
-
|
|
23
|
-
prefQuietEnd : number @description "Scratch: per-user quiet-hours end (hour 0-23) from the matched preference row." @synonyms "user quiet end" = 0
|
|
22
|
+
prefQuietHours : string @description "Scratch: per-user quiet-hours window \"HH:MM-HH:MM\" from the matched preference row; blank = none." @synonyms "user quiet hours" = ""
|
|
24
23
|
pushEndpoint : string @description "Scratch: push endpoint of the recipient's subscription for the current dispatch." @synonyms "subscription endpoint" = ""
|
|
25
24
|
pushP256dh : string @description "Scratch: p256dh key of the recipient's push subscription." @synonyms "push key" = ""
|
|
26
25
|
pushAuth : string @description "Scratch: auth secret of the recipient's push subscription." @synonyms "push auth" = ""
|
|
@@ -28,12 +27,13 @@ orbital NotifyOnEventOrbital {
|
|
|
28
27
|
|
|
29
28
|
entity NotificationPreference [persistent: notificationPreferences] {
|
|
30
29
|
id : string!
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
personId : string! @description "Owner of this preference row." @synonyms "user, owner, account"
|
|
31
|
+
eventKey : string @description "Event key this preference applies to (matches the listener's eventKey); blank = all events." @synonyms "event, trigger, subscription" = ""
|
|
32
|
+
channel : "in_app" | "email" | "push" | "slack" | "sms" | "webhook" @description "Channel override for this user." @synonyms "preferred channel, delivery method" = in_app
|
|
33
|
+
mode : "instant" | "digest" | "off" @description "Delivery mode: instant, digest, or off." @synonyms "delivery mode, frequency, cadence" = instant
|
|
34
|
+
quietHours : string @description "Per-user quiet-hours window as \"HH:MM-HH:MM\" (e.g. 22:00-07:00); blank = disabled." @synonyms "do not disturb, quiet window, night mode" = ""
|
|
35
|
+
createdAt : number @description "Timestamp indicating when this preference row was created." @synonyms "creation time, created" = 0
|
|
36
|
+
updatedAt : number @description "Timestamp indicating when this preference row was last updated." @synonyms "modified, last updated" = 0
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
entity PushSubscription [persistent: pushSubscriptions] {
|
|
@@ -72,10 +72,9 @@ orbital NotifyOnEventOrbital {
|
|
|
72
72
|
severity: @entity.severity
|
|
73
73
|
})
|
|
74
74
|
NOTIFY_PREFS_LOADED -> idle
|
|
75
|
-
(set @entity.prefMode (if ["=", (array/len (array/filter ?data (fn p (and ["=", (object/get @p
|
|
76
|
-
(set @entity.prefChannel (if ["=", (array/len (array/filter ?data (fn p (and ["=", (object/get @p
|
|
77
|
-
(set @entity.
|
|
78
|
-
(set @entity.prefQuietEnd (if ["=", (array/len (array/filter ?data (fn p (and ["=", (object/get @p userId), @entity.recipient] (or ["=", (object/get @p eventType), ""] ["=", (object/get @p eventType), @config.eventKey]))))), 0] 0 (object/get (array/first (array/filter ?data (fn p (and ["=", (object/get @p userId), @entity.recipient] (or ["=", (object/get @p eventType), ""] ["=", (object/get @p eventType), @config.eventKey]))))) quietEndHour)))
|
|
75
|
+
(set @entity.prefMode (if ["=", (array/len (array/filter ?data (fn p (and ["=", (object/get @p personId), @entity.recipient] (or ["=", (object/get @p eventKey), ""] ["=", (object/get @p eventKey), @config.eventKey]))))), 0] "instant" (object/get (array/first (array/filter ?data (fn p (and ["=", (object/get @p personId), @entity.recipient] (or ["=", (object/get @p eventKey), ""] ["=", (object/get @p eventKey), @config.eventKey]))))) mode)))
|
|
76
|
+
(set @entity.prefChannel (if ["=", (array/len (array/filter ?data (fn p (and ["=", (object/get @p personId), @entity.recipient] (or ["=", (object/get @p eventKey), ""] ["=", (object/get @p eventKey), @config.eventKey]))))), 0] "" (object/get (array/first (array/filter ?data (fn p (and ["=", (object/get @p personId), @entity.recipient] (or ["=", (object/get @p eventKey), ""] ["=", (object/get @p eventKey), @config.eventKey]))))) channel)))
|
|
77
|
+
(set @entity.prefQuietHours (if ["=", (array/len (array/filter ?data (fn p (and ["=", (object/get @p personId), @entity.recipient] (or ["=", (object/get @p eventKey), ""] ["=", (object/get @p eventKey), @config.eventKey]))))), 0] "" (object/get (array/first (array/filter ?data (fn p (and ["=", (object/get @p personId), @entity.recipient] (or ["=", (object/get @p eventKey), ""] ["=", (object/get @p eventKey), @config.eventKey]))))) quietHours)))
|
|
79
78
|
(set @entity.channel (if ["=", @entity.prefChannel, ""] @entity.channel @entity.prefChannel))
|
|
80
79
|
(if ["=", @entity.prefMode, "off"] (persist create NotificationRecord {
|
|
81
80
|
channel: @entity.channel
|
|
@@ -93,7 +92,7 @@ orbital NotifyOnEventOrbital {
|
|
|
93
92
|
recipient: @entity.recipient
|
|
94
93
|
recordedAt: @now
|
|
95
94
|
severity: @entity.severity
|
|
96
|
-
}) (if (and (
|
|
95
|
+
}) (if (and (str/includes @entity.prefQuietHours "-") ["!=", (str/slice (array/first (str/split @entity.prefQuietHours "-")) 0 2), (str/slice (array/nth (str/split @entity.prefQuietHours "-") 1) 0 2)] (if ["<", (str/slice (array/first (str/split @entity.prefQuietHours "-")) 0 2), (str/slice (array/nth (str/split @entity.prefQuietHours "-") 1) 0 2)] (and [">=", (time/hour @now), (str/slice (array/first (str/split @entity.prefQuietHours "-")) 0 2)] ["<", (time/hour @now), (str/slice (array/nth (str/split @entity.prefQuietHours "-") 1) 0 2)]) (or [">=", (time/hour @now), (str/slice (array/first (str/split @entity.prefQuietHours "-")) 0 2)] ["<", (time/hour @now), (str/slice (array/nth (str/split @entity.prefQuietHours "-") 1) 0 2)]))) (persist create NotificationRecord {
|
|
97
96
|
channel: @entity.channel
|
|
98
97
|
dispatched: false
|
|
99
98
|
error: "quiet-hours"
|
|
@@ -101,7 +100,7 @@ orbital NotifyOnEventOrbital {
|
|
|
101
100
|
recipient: @entity.recipient
|
|
102
101
|
recordedAt: @now
|
|
103
102
|
severity: @entity.severity
|
|
104
|
-
}) (if ["=", @entity.channel, in-app] (do (set @entity.dispatched true) (notify @config.severity @config.template)) (fetch NotificationRecord {
|
|
103
|
+
}) (if (or ["=", @entity.channel, in-app] ["=", @entity.channel, in_app]) (do (set @entity.dispatched true) (notify @config.severity @config.template)) (fetch NotificationRecord {
|
|
105
104
|
emit: { failure: NOTIFY_LOOKUP_FAILED, success: NOTIFY_CAP_LOADED }
|
|
106
105
|
filter: [">", (object/get @entity recordedAt), ["-", @now, 604800000]]
|
|
107
106
|
})))))
|
|
@@ -380,12 +379,12 @@ orbital NotifyOnEventOrbital {
|
|
|
380
379
|
@tier "internal"
|
|
381
380
|
eventKey : string = ""
|
|
382
381
|
@label "Event key for preferences"
|
|
383
|
-
@description "Stable key naming the upstream event this listener is bound to (e.g. 'OrderPlaced'). NotificationPreference rows (std-notification-center) whose
|
|
382
|
+
@description "Stable key naming the upstream event this listener is bound to (e.g. 'OrderPlaced'). NotificationPreference rows (std-notification-center) whose eventKey equals this key — or is blank — apply to this listener's dispatches."
|
|
384
383
|
@synonyms "preference key, event name, subscription key, notification topic"
|
|
385
384
|
@tier "policy"
|
|
386
385
|
notifyChannel : "in-app" | "email" | "sms" | "push" | "webhook" = in-app
|
|
387
386
|
@label "Channel"
|
|
388
|
-
@description "Delivery channel. `in-app` (default) emits the in-app `notify` toast. `email`/`sms`/`webhook` dispatch through call-service at event time (email.send / twilio.sendSMS / webhook.send) and persist a NotificationRecord audit row with an honest `dispatched` flag: true only once the service call succeeds, false plus the failure detail in `error` otherwise. `push` looks up the recipient's PushSubscription row (captured by std-notification-center's subscribe flow) and dispatches web-push via push.send — a recipient with no subscription records the audit row undispatched (`no-push-subscription`). `webhook` requires `webhookUrl` to actually dispatch; a blank `webhookUrl` still records the audit row undispatched rather than failing. A per-user NotificationPreference row
|
|
387
|
+
@description "Delivery channel. `in-app` (default) emits the in-app `notify` toast. `email`/`sms`/`webhook` dispatch through call-service at event time (email.send / twilio.sendSMS / webhook.send) and persist a NotificationRecord audit row with an honest `dispatched` flag: true only once the service call succeeds, false plus the failure detail in `error` otherwise. `push` looks up the recipient's PushSubscription row (captured by std-notification-center's subscribe flow) and dispatches web-push via push.send — a recipient with no subscription records the audit row undispatched (`no-push-subscription`). `webhook` requires `webhookUrl` to actually dispatch; a blank `webhookUrl` still records the audit row undispatched rather than failing. A per-user NotificationPreference row's `channel` overrides this value for that recipient."
|
|
389
388
|
@synonyms "delivery method, notification channel, send via, dispatch through, push notification, web push"
|
|
390
389
|
@tier "policy"
|
|
391
390
|
emailSubject : string = ""
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
app std-gantt-schedule "1.0.0"
|
|
2
|
+
"std-gantt-schedule — GENERIC view-only Gantt/timeline atom: fetches a task collection on INIT and renders it as bars on a day-scale axis via the gantt pattern, with group headers, dependency arrows, and a today marker. Fully rebindable: a host overrides the linked entity and points the field-mapping configs (labelField/startField/endField/durationField/statusField/groupField) at its own columns; clicking a bar emits the config-named onBarClick event with the row id. No drag-edit, no zoom — schedule viewing only."
|
|
3
|
+
type GanttLinkSpec = { from : string!, to : string! }
|
|
4
|
+
orbital GanttScheduleOrbital {
|
|
5
|
+
type TaskLoaded = Event { data : [Task] } "Fired when Task finishes loading; payload.data holds the tasks"
|
|
6
|
+
|
|
7
|
+
type TaskLoadFailed = Event { error : string, code : string } "Fired when Task fails to load"
|
|
8
|
+
|
|
9
|
+
entity Task [runtime] {
|
|
10
|
+
id : string!
|
|
11
|
+
title : string @description "task label shown on the bar" @synonyms "name, label, summary, subject" = ""
|
|
12
|
+
start : datetime @description "task start timestamp the bar is placed from" @synonyms "starts, begins, start date, from"
|
|
13
|
+
end : datetime @description "task end timestamp the bar is placed to; when absent, duration days are used" @synonyms "finish, due, end date, until"
|
|
14
|
+
status : "pending" | "active" | "complete" | "blocked" @description "task lifecycle status, drives bar colour" @synonyms "state, stage, phase" = pending
|
|
15
|
+
group : string @description "group/section the task renders under a header row for" @synonyms "team, swimlane, section, project" = ""
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
trait TaskGanttSchedule -> @rebindable Task [interaction, collection] {
|
|
19
|
+
initial: loading
|
|
20
|
+
|
|
21
|
+
state loading {
|
|
22
|
+
INIT -> loading
|
|
23
|
+
(fetch Task {
|
|
24
|
+
emit: { failure: TaskLoadFailed, success: TaskLoaded }
|
|
25
|
+
include: @config.include
|
|
26
|
+
})
|
|
27
|
+
(render-ui main {
|
|
28
|
+
align: center
|
|
29
|
+
children: [{ type: spinner }, {
|
|
30
|
+
color: muted
|
|
31
|
+
content: "Loading schedule…"
|
|
32
|
+
type: typography
|
|
33
|
+
variant: caption
|
|
34
|
+
}]
|
|
35
|
+
className: py-12
|
|
36
|
+
direction: vertical
|
|
37
|
+
gap: md
|
|
38
|
+
type: stack
|
|
39
|
+
})
|
|
40
|
+
TaskLoaded -> chart
|
|
41
|
+
(render-ui main {
|
|
42
|
+
children: [{
|
|
43
|
+
align: center
|
|
44
|
+
children: [{ name: gantt-chart, size: xl, type: icon }, {
|
|
45
|
+
content: @config.title
|
|
46
|
+
type: typography
|
|
47
|
+
variant: h2
|
|
48
|
+
}]
|
|
49
|
+
direction: horizontal
|
|
50
|
+
gap: sm
|
|
51
|
+
type: stack
|
|
52
|
+
}, {
|
|
53
|
+
barClickEvent: @config.onBarClick
|
|
54
|
+
dayWidth: @config.dayWidth
|
|
55
|
+
durationField: @config.durationField
|
|
56
|
+
endField: @config.endField
|
|
57
|
+
groupField: @config.groupField
|
|
58
|
+
links: @config.links
|
|
59
|
+
rangeEnd: @config.rangeEnd
|
|
60
|
+
rangeStart: @config.rangeStart
|
|
61
|
+
showToday: @config.showToday
|
|
62
|
+
startField: @config.startField
|
|
63
|
+
statusField: @config.statusField
|
|
64
|
+
tasks: ?data
|
|
65
|
+
titleField: @config.labelField
|
|
66
|
+
type: gantt
|
|
67
|
+
}]
|
|
68
|
+
direction: vertical
|
|
69
|
+
gap: lg
|
|
70
|
+
type: stack
|
|
71
|
+
})
|
|
72
|
+
TaskLoadFailed -> error
|
|
73
|
+
(render-ui main {
|
|
74
|
+
align: center
|
|
75
|
+
children: [{
|
|
76
|
+
color: error
|
|
77
|
+
name: alert-triangle
|
|
78
|
+
size: lg
|
|
79
|
+
type: icon
|
|
80
|
+
}, {
|
|
81
|
+
content: "Failed to load"
|
|
82
|
+
type: typography
|
|
83
|
+
variant: h3
|
|
84
|
+
}, {
|
|
85
|
+
color: muted
|
|
86
|
+
content: ?error
|
|
87
|
+
type: typography
|
|
88
|
+
variant: body
|
|
89
|
+
}, {
|
|
90
|
+
action: RETRY
|
|
91
|
+
icon: rotate-ccw
|
|
92
|
+
label: Retry
|
|
93
|
+
type: button
|
|
94
|
+
variant: primary
|
|
95
|
+
}]
|
|
96
|
+
className: py-12
|
|
97
|
+
direction: vertical
|
|
98
|
+
gap: md
|
|
99
|
+
type: stack
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
state chart {
|
|
104
|
+
INIT -> loading
|
|
105
|
+
(fetch Task {
|
|
106
|
+
emit: { failure: TaskLoadFailed, success: TaskLoaded }
|
|
107
|
+
include: @config.include
|
|
108
|
+
})
|
|
109
|
+
(render-ui main { type: spinner })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
state error {
|
|
113
|
+
INIT -> loading
|
|
114
|
+
(fetch Task {
|
|
115
|
+
emit: { failure: TaskLoadFailed, success: TaskLoaded }
|
|
116
|
+
include: @config.include
|
|
117
|
+
})
|
|
118
|
+
(render-ui main { type: spinner })
|
|
119
|
+
RETRY -> loading
|
|
120
|
+
(fetch Task {
|
|
121
|
+
emit: { failure: TaskLoadFailed, success: TaskLoaded }
|
|
122
|
+
include: @config.include
|
|
123
|
+
})
|
|
124
|
+
(render-ui main { type: spinner })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
emits {
|
|
128
|
+
TaskLoaded
|
|
129
|
+
@description "Indicates that schedule tasks have been successfully retrieved."
|
|
130
|
+
@synonyms "loaded, fetched, retrieved, populated"
|
|
131
|
+
@tier "domain"
|
|
132
|
+
TaskLoadFailed
|
|
133
|
+
@description "Indicates a failure to load schedule tasks."
|
|
134
|
+
@synonyms "error, failed, problem, loading issue"
|
|
135
|
+
@tier "internal"
|
|
136
|
+
@config.onBarClick -> external {
|
|
137
|
+
id : string
|
|
138
|
+
}
|
|
139
|
+
@description "Emitted when a gantt bar is clicked; { id } carries the clicked task row's id."
|
|
140
|
+
@tier "presentation"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
config {
|
|
144
|
+
title : string = "Schedule"
|
|
145
|
+
@label "Title"
|
|
146
|
+
@description "Header text shown above the gantt chart."
|
|
147
|
+
@tier "presentation"
|
|
148
|
+
labelField : string = "title"
|
|
149
|
+
@label "Label field"
|
|
150
|
+
@description "Row column holding the bar label. Rebound hosts point this at their own column (e.g. name, summary)."
|
|
151
|
+
@synonyms "title field, label column, name field"
|
|
152
|
+
@tier "presentation"
|
|
153
|
+
startField : string = "start"
|
|
154
|
+
@label "Start field"
|
|
155
|
+
@description "Row column holding the start timestamp each bar is placed from (e.g. startTime, startsAt, beginsAt)."
|
|
156
|
+
@synonyms "start column, from field, begin field"
|
|
157
|
+
@tier "presentation"
|
|
158
|
+
endField : string = "end"
|
|
159
|
+
@label "End field"
|
|
160
|
+
@description "Row column holding the end timestamp each bar is placed to. When a row has no end, durationField (days) is used instead."
|
|
161
|
+
@synonyms "end column, due field, finish field"
|
|
162
|
+
@tier "presentation"
|
|
163
|
+
durationField : string = ""
|
|
164
|
+
@label "Duration field"
|
|
165
|
+
@description "Row column holding the task length in days, used when the row has no end value. Empty = bars always read endField."
|
|
166
|
+
@synonyms "duration column, length field, days field"
|
|
167
|
+
@tier "presentation"
|
|
168
|
+
statusField : string = "status"
|
|
169
|
+
@label "Status field"
|
|
170
|
+
@description "Row column holding the status value that drives bar colour."
|
|
171
|
+
@synonyms "status column, state field, stage field"
|
|
172
|
+
@tier "presentation"
|
|
173
|
+
groupField : string = ""
|
|
174
|
+
@label "Group field"
|
|
175
|
+
@description "Row column rows are grouped under header rows by (e.g. team, project). Empty (default) = flat list, no group headers."
|
|
176
|
+
@synonyms "group column, swimlane field, section field"
|
|
177
|
+
@tier "presentation"
|
|
178
|
+
rangeStart : string = ""
|
|
179
|
+
@label "Range start"
|
|
180
|
+
@description "First visible day (ISO date). Empty (default) = 2 days before the earliest task."
|
|
181
|
+
@synonyms "window start, axis start, from date"
|
|
182
|
+
@tier "presentation"
|
|
183
|
+
rangeEnd : string = ""
|
|
184
|
+
@label "Range end"
|
|
185
|
+
@description "Last visible day (ISO date). Empty (default) = 2 days after the latest task end."
|
|
186
|
+
@synonyms "window end, axis end, to date"
|
|
187
|
+
@tier "presentation"
|
|
188
|
+
dayWidth : number = 28
|
|
189
|
+
@label "Day width"
|
|
190
|
+
@description "Pixels per day on the time axis."
|
|
191
|
+
@tier "presentation"
|
|
192
|
+
showToday : boolean = true
|
|
193
|
+
@label "Show today marker"
|
|
194
|
+
@description "Paint the today marker line when today falls inside the visible range."
|
|
195
|
+
@tier "presentation"
|
|
196
|
+
links : [GanttLinkSpec] = []
|
|
197
|
+
@label "Dependency links"
|
|
198
|
+
@description "Dependency arrows between task ids ({ from, to }); the host derives them from its own relation field and passes them here."
|
|
199
|
+
@synonyms "dependencies, dependency arrows, precedences"
|
|
200
|
+
@tier "presentation"
|
|
201
|
+
include : [string] = []
|
|
202
|
+
@label "Which relation fields to hydrate?"
|
|
203
|
+
@description "Entity relation field names to hydrate on fetch, so field mappings can use dot-paths like assignee.name. Empty = no hydration, the default."
|
|
204
|
+
@synonyms "relation hydration, populate relations, joined fields"
|
|
205
|
+
@tier "internal"
|
|
206
|
+
onBarClick : event = ""
|
|
207
|
+
@label "On bar click"
|
|
208
|
+
@description "Event emitted when a bar is clicked: UI:{onBarClick} with { id } carrying the clicked row's id. Empty means bars are not clickable."
|
|
209
|
+
@synonyms "bar click, task click, select task, open task"
|
|
210
|
+
@tier "presentation"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
page "/tasks/gantt" as TaskGanttSchedulePage -> TaskGanttSchedule
|
|
215
|
+
}
|
|
@@ -23,7 +23,7 @@ orbital TextareaOrbital {
|
|
|
23
23
|
|
|
24
24
|
emits {
|
|
25
25
|
@config.action -> external { }
|
|
26
|
-
@description "Declarative event
|
|
26
|
+
@description "Declarative event: fires on ⌘/Ctrl+Enter with `{ value }`."
|
|
27
27
|
@tier "essential"
|
|
28
28
|
@config.onChange -> external {
|
|
29
29
|
value : string!
|
|
@@ -47,7 +47,7 @@ orbital TextareaOrbital {
|
|
|
47
47
|
@tier "presentation"
|
|
48
48
|
action : event = "ACTION"
|
|
49
49
|
@label "Action"
|
|
50
|
-
@description "Declarative event
|
|
50
|
+
@description "Declarative event: fires on ⌘/Ctrl+Enter with `{ value }`."
|
|
51
51
|
@tier "presentation"
|
|
52
52
|
error : string
|
|
53
53
|
@label "Error"
|