@almadar/std 16.222.0 → 16.224.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.
@@ -1,6 +1,7 @@
1
1
  app std-data-import "1.0.0"
2
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
+ type DataImportFile = { name : string!, size : number, type : string, content : string }
4
5
 
5
6
  entity ImportBatch [persistent: import_batches] {
6
7
  id : string!
@@ -30,6 +31,24 @@ orbital DataImportOrbital {
30
31
  (set @entity.errorCount 0)
31
32
  (set @entity.mappingJson @config.fieldMappingJson)
32
33
  (set @entity.createdAt 0)
34
+ (render-ui main {
35
+ children: [{
36
+ content: (str/concat "Import " @entity.sourceName)
37
+ type: typography
38
+ variant: h3
39
+ }, {
40
+ accept: ".csv,text/csv"
41
+ action: UPLOAD_CSV
42
+ description: "Drop a CSV file here or click to browse"
43
+ icon: upload
44
+ label: "Upload CSV file"
45
+ maxFiles: 1
46
+ type: upload-drop-zone
47
+ }]
48
+ direction: vertical
49
+ gap: md
50
+ type: stack
51
+ })
33
52
  UPLOAD_CSV -> uploaded
34
53
  (set @entity.id ?batchId)
35
54
  (set @entity.sourceName (or ?sourceName @config.sourceName))
@@ -52,8 +71,17 @@ orbital DataImportOrbital {
52
71
  }
53
72
 
54
73
  state uploaded {
74
+ ;; STAGE_ROWS has no emitter yet (host-side CSV-content parsing is a
75
+ ;; still-open substrate gap, R-DATA-IMPORT-PAGES-BLANK-NO-UPLOAD-UI) —
76
+ ;; render something here too so `uploaded` is never a second blank
77
+ ;; dead end after the upload affordance's UPLOAD_CSV succeeds.
55
78
  BATCH_CREATED -> uploaded
56
79
  (set @entity.id ?id)
80
+ (render-ui main {
81
+ message: (str/concat "Import started — " @entity.sourceName ". Waiting for the file's rows to be staged.")
82
+ type: alert
83
+ variant: info
84
+ })
57
85
  STAGE_ROWS -> staged when [">", (array/len ?rows), 0]
58
86
  (set @entity.rowCount (array/len ?rows))
59
87
  (set @entity.errorCount (or ?errorCount 0))
@@ -273,8 +301,9 @@ orbital DataImportOrbital {
273
301
  UPLOAD_CSV {
274
302
  batchId : string
275
303
  sourceName : string
304
+ files : [DataImportFile]!
276
305
  }
277
- @description "Starts an import run for a CSV file upload. batchId is the client-minted batch id (idempotent upload — the host generates it so a retry never double-creates a batch); when omitted, the server-issued id is bound on BATCH_CREATED instead. The host's file UI (e.g. a sibling std-import menu) only announces the upload — parsing is host work and arrives as STAGE_ROWS."
306
+ @description "Starts an import run for a CSV file upload, fired by the idle state's own upload-drop-zone affordance. batchId is the client-minted batch id (idempotent upload — the host generates it so a retry never double-creates a batch); when omitted, the server-issued id is bound on BATCH_CREATED instead. files carries the picked file(s) (name/size/type/content) but the atom does not read content from it yet — no runtime capability exists to decode/parse it into rows (tracked: R-DATA-IMPORT-PAGES-BLANK-NO-UPLOAD-UI); STAGE_ROWS is the still-open handoff for a future host-side parser to answer with real row data."
278
307
  @synonyms "upload csv, import csv, start import, import file, bulk import"
279
308
  @tier "domain"
280
309
  STAGE_ROWS {
@@ -146,15 +146,34 @@ orbital GlobalSearchOrbital {
146
146
  event: SEARCH
147
147
  placeholder: @config.placeholder
148
148
  type: search-input
149
- }, (array/map (array/filter @entity.results (fn g (!= @g.resultsJson ""))) (fn g {
150
- type: stack
151
- direction: vertical
152
- gap: sm
153
- children: [
154
- <Typography.traits.TypographyRender content={@g.moduleKey} linkedEntity={SearchQuery} variant={h4} />,
155
- (array/map (array/filter (str/split @g.resultsJson ", ") (fn item (!= @item ""))) (fn item <Typography.traits.TypographyRender content={@item} linkedEntity={SearchQuery} variant={body} />))
156
- ]
157
- }))]
149
+ }, (if (> (array/len (array/filter @entity.results (fn g (!= @g.resultsJson "")))) 0)
150
+ {
151
+ type: stack
152
+ direction: vertical
153
+ gap: sm
154
+ children: (array/map (array/filter @entity.results (fn g (!= @g.resultsJson ""))) (fn g {
155
+ type: stack
156
+ direction: vertical
157
+ gap: sm
158
+ ;; a bare array/map result as a sibling of a static element in the SAME children list mis-renders as "Unknown pattern" in @almadar/ui (confirmed live via play_scene) — wrap it in its own container so every children element is a single node
159
+ children: [
160
+ <Typography.traits.TypographyRender content={@g.moduleKey} linkedEntity={SearchQuery} variant={h4} />,
161
+ {
162
+ type: stack
163
+ direction: vertical
164
+ gap: xs
165
+ children: (array/map (array/filter (str/split @g.resultsJson ", ") (fn item (!= @item ""))) (fn item <Typography.traits.TypographyRender content={@item} linkedEntity={SearchQuery} variant={body} />))
166
+ }
167
+ ]
168
+ }))
169
+ }
170
+ ;; children[1] must never be a bare empty array (renders nothing, and was observed to blank the whole slot) — always a single well-formed node instead; the two "no results yet" sub-cases share one empty-state node, distinguished only by its string props
171
+ {
172
+ description: (if (and @entity.fanOutDone (>= @entity.respondedCount @entity.requestedCount)) "Try a different search term or module." "Waiting for the rest of the results…")
173
+ icon: search-x
174
+ title: (if (and @entity.fanOutDone (>= @entity.respondedCount @entity.requestedCount)) "No results found" "Searching…")
175
+ type: empty-state
176
+ })]
158
177
  direction: vertical
159
178
  gap: md
160
179
  type: stack
@@ -110,7 +110,7 @@ orbital BoardOrbital {
110
110
  (set @entity.currentTitle ?title)
111
111
  (set @entity.currentDescription ?description)
112
112
  (set @entity.currentStage ?stage)
113
- (set @entity.currentNotes ?row.notes)
113
+ (set @entity.currentNotes ?notes)
114
114
  (set @entity.currentRow ?row)
115
115
  (render-ui main {
116
116
  children: [{
@@ -423,6 +423,7 @@ orbital BoardOrbital {
423
423
  title : string
424
424
  description : string
425
425
  stage : string
426
+ notes : string
426
427
  }
427
428
  @description "A card within the board has been selected or activated."
428
429
  @synonyms "select, activate, view, show"
@@ -581,6 +582,10 @@ orbital BoardOrbital {
581
582
  @label "Card id binding"
582
583
  @description "Per-card binding for the id used in action payloads."
583
584
  @tier "presentation"
585
+ cardNotesBinding : string = @item.notes
586
+ @label "Card notes binding"
587
+ @description "Per-card binding for the notes slot shown in the card detail view. Set to an empty string for hosts with no notes-equivalent field — declared as 'no notes capture', never inferred."
588
+ @tier "presentation"
584
589
  groupByField : string = stage
585
590
  @label "Group-by field"
586
591
  @description "Entity field whose value matches a column key — items are filtered into columns by it. Default 'stage'; set to 'status' (or any field) for entities that track state under a different name."
@@ -717,7 +722,7 @@ orbital BoardOrbital {
717
722
  align: center
718
723
  children: [{
719
724
  action: OPEN_CARD
720
- actionPayload: { description: @config.cardDescriptionBinding, id: @config.cardIdBinding, row: @item, stage: @config.cardStageBinding, title: @config.cardTitleBinding }
725
+ actionPayload: { description: @config.cardDescriptionBinding, id: @config.cardIdBinding, notes: @config.cardNotesBinding, row: @item, stage: @config.cardStageBinding, title: @config.cardTitleBinding }
721
726
  icon: arrow-right
722
727
  label: Open
723
728
  type: button
@@ -861,7 +866,7 @@ orbital BoardOrbital {
861
866
  variant: @col.variant
862
867
  }, {
863
868
  action: OPEN_CARD
864
- actionPayload: { description: @config.cardDescriptionBinding, id: @config.cardIdBinding, row: @item, stage: @config.cardStageBinding, title: @config.cardTitleBinding }
869
+ actionPayload: { description: @config.cardDescriptionBinding, id: @config.cardIdBinding, notes: @config.cardNotesBinding, row: @item, stage: @config.cardStageBinding, title: @config.cardTitleBinding }
865
870
  className: ml-auto
866
871
  icon: arrow-right
867
872
  label: Open