@lumiastream/ui 0.2.8-alpha.17 → 0.2.8-alpha.18

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/dist/index.js CHANGED
@@ -3785,6 +3785,16 @@ function mapSingleAlert(widget, lumiaAlertKey, ctx) {
3785
3785
  return buildUnit(widget, "alert", { alert: { ...baseAlert, events } }, ctx);
3786
3786
  }
3787
3787
 
3788
+ // src/se-import/mappers/importMeta.ts
3789
+ function buildImportMeta(widget, config) {
3790
+ return {
3791
+ source: "streamelements",
3792
+ widgetType: widget.type,
3793
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
3794
+ ...config && Object.keys(config).length > 0 ? { config } : {}
3795
+ };
3796
+ }
3797
+
3788
3798
  // src/se-import/mappers/goal.ts
3789
3799
  function currentForListener(listener) {
3790
3800
  if (!listener) return "0";
@@ -3860,9 +3870,12 @@ function mapGoal(widget, ctx) {
3860
3870
  border: "solid 1px transparent",
3861
3871
  borderRadius: "40px",
3862
3872
  highlightColor: "inherit",
3863
- // SE-only fields preserved as provenance no native Lumia equivalent.
3864
- se_source_listener: listener,
3865
- se_simple_design: !!v.simpleDesign
3873
+ // Source-side knobs without a native Lumia equivalent live under the
3874
+ // generic `importMeta` envelope (see mappers/importMeta.ts).
3875
+ importMeta: buildImportMeta(widget, {
3876
+ sourceListener: listener,
3877
+ simpleDesign: !!v.simpleDesign
3878
+ })
3866
3879
  },
3867
3880
  css: {
3868
3881
  // `borderRadius` / `borderStyle` / `borderWidth` / `borderColor`
@@ -3885,7 +3898,7 @@ function mapGoal(widget, ctx) {
3885
3898
  }
3886
3899
 
3887
3900
  // src/se-import/mappers/custom.ts
3888
- var SE_FIELD_TYPE_TO_LUMIA = {
3901
+ var SOURCE_TO_LUMIA_FIELD_TYPE = {
3889
3902
  text: "input",
3890
3903
  number: "number",
3891
3904
  checkbox: "checkbox",
@@ -3893,43 +3906,42 @@ var SE_FIELD_TYPE_TO_LUMIA = {
3893
3906
  slider: "slider",
3894
3907
  colorpicker: "colorpicker",
3895
3908
  fontpicker: "fontpicker",
3896
- // SE's font picker variant same control surface as `fontpicker` for our
3897
- // purposes; the SE renderer pulls Google Fonts at runtime which Lumia's
3898
- // fontpicker also does.
3909
+ // SE's `googleFont` picker is functionally the same as Lumia's native
3910
+ // fontpicker (both load Google Fonts on demand).
3899
3911
  googleFont: "fontpicker",
3900
- // SE asset uploads the asset URL lands in the `value` as a CDN string,
3901
- // which our `input` renders fine as-is. Once Lumia ships dedicated asset/
3902
- // audio pickers wired through the asset library, flip these mappings.
3903
- image: "input",
3904
- "image-input": "input",
3905
- "sound-input": "input",
3906
- "video-input": "input",
3907
- // Multi-line text — `input` accepts strings of any length; the editor
3908
- // surfaces a textarea by inspecting `value.length` heuristically.
3909
- textarea: "input",
3912
+ // Asset uploads land on the native upload types added to Lumia's
3913
+ // ConfigsFieldType. `image` (legacy SE alias) folds into `imageupload`.
3914
+ image: "imageupload",
3915
+ "image-input": "imageupload",
3916
+ "sound-input": "soundupload",
3917
+ "video-input": "videoupload",
3918
+ // Multi-line text → native textarea.
3919
+ textarea: "textarea",
3920
+ // Hidden fields keep their value in `Overlay.data` but don't render a
3921
+ // control; we map to `input` + the BaseConfigField `hidden: true` flag
3922
+ // (applied below in translateConfigs).
3910
3923
  hidden: "input",
3911
- // SE's button is a "fire a JS action" trigger with no persisted value.
3912
- // Lumia has no equivalent; surface as a non-editable label.
3913
- button: "input"
3924
+ // SE-side trigger buttons native action button.
3925
+ button: "actionbutton"
3914
3926
  };
3915
- var SE_RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set(["widgetName", "widgetAuthor", "widgetDuration"]);
3916
- function translateConfigs(seFields) {
3917
- if (!seFields) return [];
3918
- return Object.entries(seFields).filter(([key]) => !SE_RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
3919
- const seType = f.type ?? "text";
3920
- return {
3927
+ var RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set(["widgetName", "widgetAuthor", "widgetDuration"]);
3928
+ function translateConfigs(sourceFields) {
3929
+ if (!sourceFields) return [];
3930
+ return Object.entries(sourceFields).filter(([key]) => !RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
3931
+ const sourceType = f.type ?? "text";
3932
+ const nativeType = SOURCE_TO_LUMIA_FIELD_TYPE[sourceType] ?? "input";
3933
+ const base = {
3921
3934
  key,
3922
3935
  label: f.label ?? key,
3923
- type: SE_FIELD_TYPE_TO_LUMIA[seType] ?? "input",
3936
+ // The emitted `type` is a native Lumia ConfigsFieldType — no
3937
+ // source-prefixed shadow field rides along. Anything that needs
3938
+ // richer control behavior should land as a new native type.
3939
+ type: nativeType,
3924
3940
  value: f.value,
3925
- options: f.options,
3926
- // Preserve the original SE type + group so a future editor UX
3927
- // upgrade can render the right control (e.g. an actual asset
3928
- // picker for `image-input`) without re-importing. Mentioned in
3929
- // docs/SE_IMPORT_GAPS.md under "Other widgets > custom-event-list".
3930
- seType,
3931
- ...f.group ? { seGroup: f.group } : {}
3941
+ options: f.options
3932
3942
  };
3943
+ if (sourceType === "hidden") base.hidden = true;
3944
+ return base;
3933
3945
  });
3934
3946
  }
3935
3947
  function mapCustom(widget, ctx) {
@@ -4058,14 +4070,17 @@ function mapChatbox(widget, ctx) {
4058
4070
  twitch: { themeConfig: { primaryColor: accentColor, showUsernameColors: true } }
4059
4071
  }
4060
4072
  } : {},
4061
- // SE-only fields preserved as provenance no native Lumia equivalent.
4062
- // `delay` / `messageDelay` (initial + per-message hold), and the
4063
- // raw highlight block in case a future Lumia feature wants the
4064
- // fine-grained styling.
4065
- se_message_delay: v.messageDelay,
4066
- se_initial_delay: v.delay,
4067
- se_highlight_users: v.highlight,
4068
- se_text_css: textCss
4073
+ // Source-side knobs preserved under the generic importMeta envelope
4074
+ // no native Lumia equivalent today. `messageDelay` is per-message
4075
+ // hold, `initialDelay` is the show-after-connect delay, and we
4076
+ // stash the raw highlight block + text css for any future feature
4077
+ // that wants the fine-grained styling.
4078
+ importMeta: buildImportMeta(widget, {
4079
+ messageDelay: v.messageDelay,
4080
+ initialDelay: v.delay,
4081
+ highlightUsers: v.highlight,
4082
+ textCss
4083
+ })
4069
4084
  }
4070
4085
  }, ctx);
4071
4086
  }
@@ -4339,12 +4354,15 @@ function mapCredits(widget, ctx) {
4339
4354
  audio: {
4340
4355
  content: { src: [], name: [], loop: false, volume: 1 }
4341
4356
  },
4342
- // SE-specific knobs preserved as provenance; no direct Lumia equivalent for
4343
- // these. `chosenFilter` would narrow eventsToShow if we had a clean mapping;
4344
- // `separator` would join entries but Lumia credits uses sectioned cards instead.
4345
- se_chosen_filter: v.chosenFilter,
4346
- se_separator: v.separator,
4347
- se_amount: v.amount
4357
+ // Source-side knobs with no direct Lumia equivalent — preserved
4358
+ // under the generic importMeta envelope. `chosenFilter` would
4359
+ // narrow eventsToShow if we had a clean mapping; `separator` would
4360
+ // join entries but Lumia credits uses sectioned cards instead.
4361
+ importMeta: buildImportMeta(widget, {
4362
+ chosenFilter: v.chosenFilter,
4363
+ separator: v.separator,
4364
+ amount: v.amount
4365
+ })
4348
4366
  },
4349
4367
  css: {
4350
4368
  fontSize: 30,
@@ -4412,8 +4430,10 @@ function mapSlideshow(widget, ctx) {
4412
4430
  enterAnimationDelay: (seAnim.inDelay ?? 0) * 1e3,
4413
4431
  exitAnimationDelay: (seAnim.outDelay ?? 0) * 1e3
4414
4432
  },
4415
- se_interval: v.interval,
4416
- se_raw_duration: v.duration
4433
+ importMeta: buildImportMeta(widget, {
4434
+ interval: v.interval,
4435
+ rawDuration: v.duration
4436
+ })
4417
4437
  }
4418
4438
  }, ctx);
4419
4439
  }
@@ -4493,9 +4513,11 @@ function mapTimer(widget, ctx) {
4493
4513
  changeMessageAfter: !!v.changeMessageAfterCountdown,
4494
4514
  countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
4495
4515
  date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
4496
- se_date: absoluteDate,
4497
- se_date_picker: v.datePicker,
4498
- se_exact_time: v.exactTime
4516
+ importMeta: buildImportMeta(widget, {
4517
+ rawDate: absoluteDate,
4518
+ datePicker: v.datePicker,
4519
+ exactTime: v.exactTime
4520
+ })
4499
4521
  }
4500
4522
  }, ctx);
4501
4523
  }
@@ -4515,7 +4537,7 @@ function mapNowPlaying(widget, ctx) {
4515
4537
  showImg: true,
4516
4538
  oneLineTitle: false,
4517
4539
  highlightColor: "inherit",
4518
- se_simple_design: !!v.simpleDesign
4540
+ importMeta: buildImportMeta(widget, { simpleDesign: !!v.simpleDesign })
4519
4541
  },
4520
4542
  css: {
4521
4543
  borderRadius: "0px",
@@ -4560,9 +4582,10 @@ function mapMediaShare(widget, ctx) {
4560
4582
  playerVisible: false,
4561
4583
  defaultVolume: 70,
4562
4584
  autoAdvanceOnEnd: true,
4563
- // SE-only knob preserved for round-trip provenance; Lumia ignores
4564
- // it (LumiaStream auto-advance fires on `durationSeconds + grace`).
4565
- se_timeLeft: v.timeLeft === true
4585
+ // Source-side knob preserved for round-trip provenance; Lumia
4586
+ // ignores it at runtime (auto-advance fires on
4587
+ // `durationSeconds + grace`).
4588
+ importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
4566
4589
  },
4567
4590
  css: {
4568
4591
  fontFamily: "Roboto",
@@ -4636,15 +4659,18 @@ function mapKappagen(widget, ctx) {
4636
4659
  ffz: v.ffz !== false,
4637
4660
  sevenTv: v.sevenTv !== false
4638
4661
  },
4639
- // SE-only knobs preserved as provenance; no Lumia rendering for these.
4640
- se_mode: v.mode,
4641
- se_cheersplosions: !!v.cheersplosions,
4642
- se_emotesplosion: v.emotesplosion,
4643
- se_emotesplosion_dir: v.emotesplosiondir,
4644
- se_emotesplosion_duration: v.emotesplosionduration,
4645
- se_spawn_zone: v.spawnzone,
4646
- se_fullscreen: !!v.fullscreen,
4647
- se_direction: v.direction
4662
+ // Source-side knobs preserved under the generic importMeta
4663
+ // envelope — no Lumia rendering for these today.
4664
+ importMeta: buildImportMeta(widget, {
4665
+ mode: v.mode,
4666
+ cheersplosions: !!v.cheersplosions,
4667
+ emotesplosion: v.emotesplosion,
4668
+ emotesplosionDir: v.emotesplosiondir,
4669
+ emotesplosionDuration: v.emotesplosionduration,
4670
+ spawnZone: v.spawnzone,
4671
+ fullscreen: !!v.fullscreen,
4672
+ direction: v.direction
4673
+ })
4648
4674
  },
4649
4675
  css: { background: "transparent" }
4650
4676
  },
@@ -4668,11 +4694,13 @@ function mapLeaderboardFromVariables(widget, currency, ctx) {
4668
4694
  showCurrentPoints: false,
4669
4695
  showAllTimePoints: true,
4670
4696
  itemGap: 8,
4671
- // Preserve SE knobs that don't map cleanly so they survive the round-trip
4672
- // and the user can re-introspect later if behaviour differs.
4673
- se_period: v.period,
4674
- se_offset: v.offset,
4675
- se_layout: v.layout
4697
+ // Source-side knobs that don't map cleanly survive under the
4698
+ // generic importMeta envelope so the user can re-introspect.
4699
+ importMeta: buildImportMeta(widget, {
4700
+ period: v.period,
4701
+ offset: v.offset,
4702
+ layout: v.layout
4703
+ })
4676
4704
  }
4677
4705
  }, ctx);
4678
4706
  }
@@ -4702,8 +4730,6 @@ function mapGiveaway(widget, ctx) {
4702
4730
  // preference via provenance — a future renderer can read it without us
4703
4731
  // having lied about the toggle. Same for `displayTicketsPurchased`.
4704
4732
  hideParticipantOnStart: false,
4705
- se_displayEntrants: v.displayEntrants !== false,
4706
- se_displayTicketsPurchased: v.displayTicketsPurchased !== false,
4707
4733
  removeWhenRedeemed: false,
4708
4734
  showWinningModal: true,
4709
4735
  playSoundFx: true,
@@ -4720,12 +4746,18 @@ function mapGiveaway(widget, ctx) {
4720
4746
  raffleEndedText: "The raffle has ended, and the selection of the winner will take place shortly.",
4721
4747
  raffleTimerText: "Time since raffle started",
4722
4748
  raffleCountdownText: "Raffle will end after",
4723
- // SE-specific knobs with no Lumia equivalent — preserved as provenance.
4724
- se_timeout: v.timeout,
4725
- se_enable_timeout: v.enableTimeOut,
4726
- se_display_entrants: v.displayEntrants,
4727
- se_display_tickets_purchased: v.displayTicketsPurchased,
4728
- se_image_style_width: v.imageStyle?.width
4749
+ // Source-side knobs with no Lumia equivalent — preserved under
4750
+ // the generic importMeta envelope. `displayEntrants` / `displayTicketsPurchased`
4751
+ // are SE's persistent "never show" toggles; Lumia has no equivalent
4752
+ // today (its `hideParticipantOnStart` is transient), so we preserve
4753
+ // the preference for a future renderer.
4754
+ importMeta: buildImportMeta(widget, {
4755
+ timeout: v.timeout,
4756
+ enableTimeout: v.enableTimeOut,
4757
+ displayEntrants: v.displayEntrants,
4758
+ displayTicketsPurchased: v.displayTicketsPurchased,
4759
+ imageStyleWidth: v.imageStyle?.width
4760
+ })
4729
4761
  }
4730
4762
  }, ctx);
4731
4763
  }
@@ -4795,11 +4827,12 @@ function mapHypetrain(widget, ctx) {
4795
4827
  trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
4796
4828
  rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
4797
4829
  infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
4798
- // SE config preserved verbatim as provenance no Lumia equivalent for
4799
- // most of these knobs (per-widget animation tunables, sound URLs the
4800
- // streamer might want to migrate manually, etc.). Users can inspect
4801
- // these via the overlay JSON if they want to recreate a specific look.
4802
- se_variables: v
4830
+ // Source config preserved verbatim under the generic importMeta
4831
+ // envelope no Lumia equivalent for most of these knobs
4832
+ // (per-widget animation tunables, sound URLs the streamer might
4833
+ // want to migrate manually, etc.). Users can inspect via the
4834
+ // overlay JSON if they want to recreate a specific look.
4835
+ importMeta: buildImportMeta(widget, { variables: v })
4803
4836
  }
4804
4837
  }, ctx);
4805
4838
  }
@@ -4934,13 +4967,16 @@ function mapHypeCup(widget, ctx) {
4934
4967
  // can enable in Settings; SE has no equivalent so nothing to lift.
4935
4968
  goal: { active: false, currency: "tips", target: 100, current: 0, readoutTemplate: "{{current}} / {{target}}", textColor: "#ffffff", hitAt: null },
4936
4969
  break: { enabled: false, command: "!breakjar", rebuildDelay: 5e3 },
4937
- // SE-only knobs that don't have a Lumia equivalent — preserved as
4938
- // provenance for future phases (per-cup polygons + enter/movement
4939
- // physics tweaks). `se_types` removed — now consumed into sprites above.
4940
- se_enter_from: v.enterFrom ?? null,
4941
- se_prevent_movement: !!v.preventMovement,
4942
- se_fullscreen: !!v.fullscreen,
4943
- se_cups: v.cups ?? null
4970
+ // Source-side knobs without a Lumia equivalent — preserved under
4971
+ // the generic `importMeta` envelope for future phases (per-cup
4972
+ // polygons + enter/movement physics tweaks). The per-event
4973
+ // sprite sets were already lifted into native `sprites` above.
4974
+ importMeta: buildImportMeta(widget, {
4975
+ enterFrom: v.enterFrom ?? null,
4976
+ preventMovement: !!v.preventMovement,
4977
+ fullscreen: !!v.fullscreen,
4978
+ cups: v.cups ?? null
4979
+ })
4944
4980
  },
4945
4981
  css: {
4946
4982
  background: "transparent"
@@ -5059,17 +5095,19 @@ function mapStreamBoss(widget, ctx) {
5059
5095
  bossNameColor: "",
5060
5096
  botChat: "off",
5061
5097
  chatAsSelf: false,
5062
- // SE-only knobs preserved as provenance for future passes. None
5063
- // have direct Lumia analogs today:
5064
- // - `right` anchors the boss to the right edge of its bounds;
5065
- // Lumia uses layer position instead, so this is informational.
5066
- // - `bossName` controls vertical name position in SE; our themes
5067
- // don't expose that level of fine-grained tweaking yet.
5068
- // - `mode` SE's gameplay mode hint ('overkill', etc.).
5069
- se_right: !!v.right,
5070
- se_bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
5071
- se_mode: typeof v.mode === "string" ? v.mode : null,
5072
- se_showName: v.showName !== false
5098
+ // Source-side knobs preserved as provenance for future passes.
5099
+ // None have direct Lumia analogs today:
5100
+ // - `right` anchors the boss to the right edge of its
5101
+ // bounds; Lumia uses layer position instead.
5102
+ // - `bossNamePosition` SE-controlled vertical name position; our
5103
+ // themes don't expose that level of tweaking.
5104
+ // - `mode` source-side gameplay mode hint ('overkill').
5105
+ importMeta: buildImportMeta(widget, {
5106
+ right: !!v.right,
5107
+ bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
5108
+ mode: typeof v.mode === "string" ? v.mode : null,
5109
+ showName: v.showName !== false
5110
+ })
5073
5111
  },
5074
5112
  css: {
5075
5113
  background: "transparent"
@@ -7911,6 +7949,7 @@ function extractErrorMessage(err) {
7911
7949
  }
7912
7950
 
7913
7951
  // src/se-import/index.ts
7952
+ var IMPORT_META_KEY = "importMeta";
7914
7953
  var REVIEW_REASONS = {
7915
7954
  "se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
7916
7955
  // `se-widget-hype-cup` intentionally omitted — it now routes to the native
@@ -8057,7 +8096,14 @@ function importSEBootstrap(bootstrap) {
8057
8096
  lights: [],
8058
8097
  css: {},
8059
8098
  content: {
8060
- se_provenance: { widget: w, reason: "Imported as native group", status: "group" }
8099
+ [IMPORT_META_KEY]: {
8100
+ source: "streamelements",
8101
+ widgetType: w.type,
8102
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8103
+ widget: w,
8104
+ reason: "Imported as native group",
8105
+ status: "group"
8106
+ }
8061
8107
  },
8062
8108
  events: {},
8063
8109
  variables: {}
@@ -8080,7 +8126,10 @@ function importSEBootstrap(bootstrap) {
8080
8126
  if (result.status === "placeholder" || result.status === "template") {
8081
8127
  module.content = {
8082
8128
  ...module.content ?? {},
8083
- se_provenance: {
8129
+ [IMPORT_META_KEY]: {
8130
+ source: "streamelements",
8131
+ widgetType: w.type,
8132
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8084
8133
  widget: w,
8085
8134
  reason: reasonFor(w.type, result.status, result.flaggedOff),
8086
8135
  status: result.status,
@@ -8161,7 +8210,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8161
8210
  bounds: existingLayer.bounds,
8162
8211
  state: existingLayer.state
8163
8212
  };
8164
- const provenance2 = existing2.content?.se_provenance;
8213
+ const importMeta2 = existing2.content?.[IMPORT_META_KEY];
8165
8214
  const mergedLights = (existing2.lights?.length ?? 0) > 0 ? existing2.lights : transplant.module.lights;
8166
8215
  const mergedEvents = existing2.events && Object.keys(existing2.events).length > 0 ? { ...transplant.module.events, ...existing2.events } : transplant.module.events;
8167
8216
  const mergedVariables = existing2.variables && Object.keys(existing2.variables).length > 0 ? { ...transplant.module.variables, ...existing2.variables } : transplant.module.variables;
@@ -8171,7 +8220,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8171
8220
  lights: mergedLights,
8172
8221
  events: mergedEvents,
8173
8222
  variables: mergedVariables,
8174
- content: { ...transplant.module.content ?? {}, se_provenance: provenance2 }
8223
+ content: { ...transplant.module.content ?? {}, [IMPORT_META_KEY]: importMeta2 }
8175
8224
  };
8176
8225
  const rootOldNewId = transplant.layer.id;
8177
8226
  const remappedExtras = (transplant.extras ?? []).map((e) => ({
@@ -8198,7 +8247,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8198
8247
  const existing = modules[moduleId];
8199
8248
  const generated = payload;
8200
8249
  if (!existing || !generated) return result;
8201
- const provenance = existing.content?.se_provenance;
8250
+ const importMeta = existing.content?.[IMPORT_META_KEY];
8202
8251
  const replaced = {
8203
8252
  ...existing,
8204
8253
  settings: { ...existing.settings ?? {}, type: "custom" },
@@ -8212,7 +8261,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8212
8261
  data: parseLooseJson(generated.data) ?? {},
8213
8262
  configs: parseLooseJson(generated.configs) ?? [],
8214
8263
  flavor: "ai-generated",
8215
- se_provenance: provenance
8264
+ [IMPORT_META_KEY]: importMeta
8216
8265
  }
8217
8266
  };
8218
8267
  modules[moduleId] = replaced;
@@ -116,6 +116,7 @@ interface SECss {
116
116
  opacity?: number;
117
117
  top?: string | number;
118
118
  left?: string | number;
119
+ transform?: string;
119
120
  }
120
121
  interface SEText {
121
122
  type?: string;
package/dist/se-import.js CHANGED
@@ -670,6 +670,16 @@ function mapSingleAlert(widget, lumiaAlertKey, ctx) {
670
670
  return buildUnit(widget, "alert", { alert: { ...baseAlert, events } }, ctx);
671
671
  }
672
672
 
673
+ // src/se-import/mappers/importMeta.ts
674
+ function buildImportMeta(widget, config) {
675
+ return {
676
+ source: "streamelements",
677
+ widgetType: widget.type,
678
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
679
+ ...config && Object.keys(config).length > 0 ? { config } : {}
680
+ };
681
+ }
682
+
673
683
  // src/se-import/mappers/goal.ts
674
684
  function currentForListener(listener) {
675
685
  if (!listener) return "0";
@@ -745,9 +755,12 @@ function mapGoal(widget, ctx) {
745
755
  border: "solid 1px transparent",
746
756
  borderRadius: "40px",
747
757
  highlightColor: "inherit",
748
- // SE-only fields preserved as provenance no native Lumia equivalent.
749
- se_source_listener: listener,
750
- se_simple_design: !!v.simpleDesign
758
+ // Source-side knobs without a native Lumia equivalent live under the
759
+ // generic `importMeta` envelope (see mappers/importMeta.ts).
760
+ importMeta: buildImportMeta(widget, {
761
+ sourceListener: listener,
762
+ simpleDesign: !!v.simpleDesign
763
+ })
751
764
  },
752
765
  css: {
753
766
  // `borderRadius` / `borderStyle` / `borderWidth` / `borderColor`
@@ -770,7 +783,7 @@ function mapGoal(widget, ctx) {
770
783
  }
771
784
 
772
785
  // src/se-import/mappers/custom.ts
773
- var SE_FIELD_TYPE_TO_LUMIA = {
786
+ var SOURCE_TO_LUMIA_FIELD_TYPE = {
774
787
  text: "input",
775
788
  number: "number",
776
789
  checkbox: "checkbox",
@@ -778,43 +791,42 @@ var SE_FIELD_TYPE_TO_LUMIA = {
778
791
  slider: "slider",
779
792
  colorpicker: "colorpicker",
780
793
  fontpicker: "fontpicker",
781
- // SE's font picker variant same control surface as `fontpicker` for our
782
- // purposes; the SE renderer pulls Google Fonts at runtime which Lumia's
783
- // fontpicker also does.
794
+ // SE's `googleFont` picker is functionally the same as Lumia's native
795
+ // fontpicker (both load Google Fonts on demand).
784
796
  googleFont: "fontpicker",
785
- // SE asset uploads the asset URL lands in the `value` as a CDN string,
786
- // which our `input` renders fine as-is. Once Lumia ships dedicated asset/
787
- // audio pickers wired through the asset library, flip these mappings.
788
- image: "input",
789
- "image-input": "input",
790
- "sound-input": "input",
791
- "video-input": "input",
792
- // Multi-line text — `input` accepts strings of any length; the editor
793
- // surfaces a textarea by inspecting `value.length` heuristically.
794
- textarea: "input",
797
+ // Asset uploads land on the native upload types added to Lumia's
798
+ // ConfigsFieldType. `image` (legacy SE alias) folds into `imageupload`.
799
+ image: "imageupload",
800
+ "image-input": "imageupload",
801
+ "sound-input": "soundupload",
802
+ "video-input": "videoupload",
803
+ // Multi-line text → native textarea.
804
+ textarea: "textarea",
805
+ // Hidden fields keep their value in `Overlay.data` but don't render a
806
+ // control; we map to `input` + the BaseConfigField `hidden: true` flag
807
+ // (applied below in translateConfigs).
795
808
  hidden: "input",
796
- // SE's button is a "fire a JS action" trigger with no persisted value.
797
- // Lumia has no equivalent; surface as a non-editable label.
798
- button: "input"
809
+ // SE-side trigger buttons native action button.
810
+ button: "actionbutton"
799
811
  };
800
- var SE_RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set(["widgetName", "widgetAuthor", "widgetDuration"]);
801
- function translateConfigs(seFields) {
802
- if (!seFields) return [];
803
- return Object.entries(seFields).filter(([key]) => !SE_RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
804
- const seType = f.type ?? "text";
805
- return {
812
+ var RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set(["widgetName", "widgetAuthor", "widgetDuration"]);
813
+ function translateConfigs(sourceFields) {
814
+ if (!sourceFields) return [];
815
+ return Object.entries(sourceFields).filter(([key]) => !RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
816
+ const sourceType = f.type ?? "text";
817
+ const nativeType = SOURCE_TO_LUMIA_FIELD_TYPE[sourceType] ?? "input";
818
+ const base = {
806
819
  key,
807
820
  label: f.label ?? key,
808
- type: SE_FIELD_TYPE_TO_LUMIA[seType] ?? "input",
821
+ // The emitted `type` is a native Lumia ConfigsFieldType — no
822
+ // source-prefixed shadow field rides along. Anything that needs
823
+ // richer control behavior should land as a new native type.
824
+ type: nativeType,
809
825
  value: f.value,
810
- options: f.options,
811
- // Preserve the original SE type + group so a future editor UX
812
- // upgrade can render the right control (e.g. an actual asset
813
- // picker for `image-input`) without re-importing. Mentioned in
814
- // docs/SE_IMPORT_GAPS.md under "Other widgets > custom-event-list".
815
- seType,
816
- ...f.group ? { seGroup: f.group } : {}
826
+ options: f.options
817
827
  };
828
+ if (sourceType === "hidden") base.hidden = true;
829
+ return base;
818
830
  });
819
831
  }
820
832
  function mapCustom(widget, ctx) {
@@ -943,14 +955,17 @@ function mapChatbox(widget, ctx) {
943
955
  twitch: { themeConfig: { primaryColor: accentColor, showUsernameColors: true } }
944
956
  }
945
957
  } : {},
946
- // SE-only fields preserved as provenance no native Lumia equivalent.
947
- // `delay` / `messageDelay` (initial + per-message hold), and the
948
- // raw highlight block in case a future Lumia feature wants the
949
- // fine-grained styling.
950
- se_message_delay: v.messageDelay,
951
- se_initial_delay: v.delay,
952
- se_highlight_users: v.highlight,
953
- se_text_css: textCss
958
+ // Source-side knobs preserved under the generic importMeta envelope
959
+ // no native Lumia equivalent today. `messageDelay` is per-message
960
+ // hold, `initialDelay` is the show-after-connect delay, and we
961
+ // stash the raw highlight block + text css for any future feature
962
+ // that wants the fine-grained styling.
963
+ importMeta: buildImportMeta(widget, {
964
+ messageDelay: v.messageDelay,
965
+ initialDelay: v.delay,
966
+ highlightUsers: v.highlight,
967
+ textCss
968
+ })
954
969
  }
955
970
  }, ctx);
956
971
  }
@@ -1224,12 +1239,15 @@ function mapCredits(widget, ctx) {
1224
1239
  audio: {
1225
1240
  content: { src: [], name: [], loop: false, volume: 1 }
1226
1241
  },
1227
- // SE-specific knobs preserved as provenance; no direct Lumia equivalent for
1228
- // these. `chosenFilter` would narrow eventsToShow if we had a clean mapping;
1229
- // `separator` would join entries but Lumia credits uses sectioned cards instead.
1230
- se_chosen_filter: v.chosenFilter,
1231
- se_separator: v.separator,
1232
- se_amount: v.amount
1242
+ // Source-side knobs with no direct Lumia equivalent — preserved
1243
+ // under the generic importMeta envelope. `chosenFilter` would
1244
+ // narrow eventsToShow if we had a clean mapping; `separator` would
1245
+ // join entries but Lumia credits uses sectioned cards instead.
1246
+ importMeta: buildImportMeta(widget, {
1247
+ chosenFilter: v.chosenFilter,
1248
+ separator: v.separator,
1249
+ amount: v.amount
1250
+ })
1233
1251
  },
1234
1252
  css: {
1235
1253
  fontSize: 30,
@@ -1297,8 +1315,10 @@ function mapSlideshow(widget, ctx) {
1297
1315
  enterAnimationDelay: (seAnim.inDelay ?? 0) * 1e3,
1298
1316
  exitAnimationDelay: (seAnim.outDelay ?? 0) * 1e3
1299
1317
  },
1300
- se_interval: v.interval,
1301
- se_raw_duration: v.duration
1318
+ importMeta: buildImportMeta(widget, {
1319
+ interval: v.interval,
1320
+ rawDuration: v.duration
1321
+ })
1302
1322
  }
1303
1323
  }, ctx);
1304
1324
  }
@@ -1378,9 +1398,11 @@ function mapTimer(widget, ctx) {
1378
1398
  changeMessageAfter: !!v.changeMessageAfterCountdown,
1379
1399
  countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
1380
1400
  date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
1381
- se_date: absoluteDate,
1382
- se_date_picker: v.datePicker,
1383
- se_exact_time: v.exactTime
1401
+ importMeta: buildImportMeta(widget, {
1402
+ rawDate: absoluteDate,
1403
+ datePicker: v.datePicker,
1404
+ exactTime: v.exactTime
1405
+ })
1384
1406
  }
1385
1407
  }, ctx);
1386
1408
  }
@@ -1400,7 +1422,7 @@ function mapNowPlaying(widget, ctx) {
1400
1422
  showImg: true,
1401
1423
  oneLineTitle: false,
1402
1424
  highlightColor: "inherit",
1403
- se_simple_design: !!v.simpleDesign
1425
+ importMeta: buildImportMeta(widget, { simpleDesign: !!v.simpleDesign })
1404
1426
  },
1405
1427
  css: {
1406
1428
  borderRadius: "0px",
@@ -1445,9 +1467,10 @@ function mapMediaShare(widget, ctx) {
1445
1467
  playerVisible: false,
1446
1468
  defaultVolume: 70,
1447
1469
  autoAdvanceOnEnd: true,
1448
- // SE-only knob preserved for round-trip provenance; Lumia ignores
1449
- // it (LumiaStream auto-advance fires on `durationSeconds + grace`).
1450
- se_timeLeft: v.timeLeft === true
1470
+ // Source-side knob preserved for round-trip provenance; Lumia
1471
+ // ignores it at runtime (auto-advance fires on
1472
+ // `durationSeconds + grace`).
1473
+ importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
1451
1474
  },
1452
1475
  css: {
1453
1476
  fontFamily: "Roboto",
@@ -1521,15 +1544,18 @@ function mapKappagen(widget, ctx) {
1521
1544
  ffz: v.ffz !== false,
1522
1545
  sevenTv: v.sevenTv !== false
1523
1546
  },
1524
- // SE-only knobs preserved as provenance; no Lumia rendering for these.
1525
- se_mode: v.mode,
1526
- se_cheersplosions: !!v.cheersplosions,
1527
- se_emotesplosion: v.emotesplosion,
1528
- se_emotesplosion_dir: v.emotesplosiondir,
1529
- se_emotesplosion_duration: v.emotesplosionduration,
1530
- se_spawn_zone: v.spawnzone,
1531
- se_fullscreen: !!v.fullscreen,
1532
- se_direction: v.direction
1547
+ // Source-side knobs preserved under the generic importMeta
1548
+ // envelope — no Lumia rendering for these today.
1549
+ importMeta: buildImportMeta(widget, {
1550
+ mode: v.mode,
1551
+ cheersplosions: !!v.cheersplosions,
1552
+ emotesplosion: v.emotesplosion,
1553
+ emotesplosionDir: v.emotesplosiondir,
1554
+ emotesplosionDuration: v.emotesplosionduration,
1555
+ spawnZone: v.spawnzone,
1556
+ fullscreen: !!v.fullscreen,
1557
+ direction: v.direction
1558
+ })
1533
1559
  },
1534
1560
  css: { background: "transparent" }
1535
1561
  },
@@ -1553,11 +1579,13 @@ function mapLeaderboardFromVariables(widget, currency, ctx) {
1553
1579
  showCurrentPoints: false,
1554
1580
  showAllTimePoints: true,
1555
1581
  itemGap: 8,
1556
- // Preserve SE knobs that don't map cleanly so they survive the round-trip
1557
- // and the user can re-introspect later if behaviour differs.
1558
- se_period: v.period,
1559
- se_offset: v.offset,
1560
- se_layout: v.layout
1582
+ // Source-side knobs that don't map cleanly survive under the
1583
+ // generic importMeta envelope so the user can re-introspect.
1584
+ importMeta: buildImportMeta(widget, {
1585
+ period: v.period,
1586
+ offset: v.offset,
1587
+ layout: v.layout
1588
+ })
1561
1589
  }
1562
1590
  }, ctx);
1563
1591
  }
@@ -1587,8 +1615,6 @@ function mapGiveaway(widget, ctx) {
1587
1615
  // preference via provenance — a future renderer can read it without us
1588
1616
  // having lied about the toggle. Same for `displayTicketsPurchased`.
1589
1617
  hideParticipantOnStart: false,
1590
- se_displayEntrants: v.displayEntrants !== false,
1591
- se_displayTicketsPurchased: v.displayTicketsPurchased !== false,
1592
1618
  removeWhenRedeemed: false,
1593
1619
  showWinningModal: true,
1594
1620
  playSoundFx: true,
@@ -1605,12 +1631,18 @@ function mapGiveaway(widget, ctx) {
1605
1631
  raffleEndedText: "The raffle has ended, and the selection of the winner will take place shortly.",
1606
1632
  raffleTimerText: "Time since raffle started",
1607
1633
  raffleCountdownText: "Raffle will end after",
1608
- // SE-specific knobs with no Lumia equivalent — preserved as provenance.
1609
- se_timeout: v.timeout,
1610
- se_enable_timeout: v.enableTimeOut,
1611
- se_display_entrants: v.displayEntrants,
1612
- se_display_tickets_purchased: v.displayTicketsPurchased,
1613
- se_image_style_width: v.imageStyle?.width
1634
+ // Source-side knobs with no Lumia equivalent — preserved under
1635
+ // the generic importMeta envelope. `displayEntrants` / `displayTicketsPurchased`
1636
+ // are SE's persistent "never show" toggles; Lumia has no equivalent
1637
+ // today (its `hideParticipantOnStart` is transient), so we preserve
1638
+ // the preference for a future renderer.
1639
+ importMeta: buildImportMeta(widget, {
1640
+ timeout: v.timeout,
1641
+ enableTimeout: v.enableTimeOut,
1642
+ displayEntrants: v.displayEntrants,
1643
+ displayTicketsPurchased: v.displayTicketsPurchased,
1644
+ imageStyleWidth: v.imageStyle?.width
1645
+ })
1614
1646
  }
1615
1647
  }, ctx);
1616
1648
  }
@@ -1680,11 +1712,12 @@ function mapHypetrain(widget, ctx) {
1680
1712
  trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
1681
1713
  rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
1682
1714
  infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
1683
- // SE config preserved verbatim as provenance no Lumia equivalent for
1684
- // most of these knobs (per-widget animation tunables, sound URLs the
1685
- // streamer might want to migrate manually, etc.). Users can inspect
1686
- // these via the overlay JSON if they want to recreate a specific look.
1687
- se_variables: v
1715
+ // Source config preserved verbatim under the generic importMeta
1716
+ // envelope no Lumia equivalent for most of these knobs
1717
+ // (per-widget animation tunables, sound URLs the streamer might
1718
+ // want to migrate manually, etc.). Users can inspect via the
1719
+ // overlay JSON if they want to recreate a specific look.
1720
+ importMeta: buildImportMeta(widget, { variables: v })
1688
1721
  }
1689
1722
  }, ctx);
1690
1723
  }
@@ -1819,13 +1852,16 @@ function mapHypeCup(widget, ctx) {
1819
1852
  // can enable in Settings; SE has no equivalent so nothing to lift.
1820
1853
  goal: { active: false, currency: "tips", target: 100, current: 0, readoutTemplate: "{{current}} / {{target}}", textColor: "#ffffff", hitAt: null },
1821
1854
  break: { enabled: false, command: "!breakjar", rebuildDelay: 5e3 },
1822
- // SE-only knobs that don't have a Lumia equivalent — preserved as
1823
- // provenance for future phases (per-cup polygons + enter/movement
1824
- // physics tweaks). `se_types` removed — now consumed into sprites above.
1825
- se_enter_from: v.enterFrom ?? null,
1826
- se_prevent_movement: !!v.preventMovement,
1827
- se_fullscreen: !!v.fullscreen,
1828
- se_cups: v.cups ?? null
1855
+ // Source-side knobs without a Lumia equivalent — preserved under
1856
+ // the generic `importMeta` envelope for future phases (per-cup
1857
+ // polygons + enter/movement physics tweaks). The per-event
1858
+ // sprite sets were already lifted into native `sprites` above.
1859
+ importMeta: buildImportMeta(widget, {
1860
+ enterFrom: v.enterFrom ?? null,
1861
+ preventMovement: !!v.preventMovement,
1862
+ fullscreen: !!v.fullscreen,
1863
+ cups: v.cups ?? null
1864
+ })
1829
1865
  },
1830
1866
  css: {
1831
1867
  background: "transparent"
@@ -1944,17 +1980,19 @@ function mapStreamBoss(widget, ctx) {
1944
1980
  bossNameColor: "",
1945
1981
  botChat: "off",
1946
1982
  chatAsSelf: false,
1947
- // SE-only knobs preserved as provenance for future passes. None
1948
- // have direct Lumia analogs today:
1949
- // - `right` anchors the boss to the right edge of its bounds;
1950
- // Lumia uses layer position instead, so this is informational.
1951
- // - `bossName` controls vertical name position in SE; our themes
1952
- // don't expose that level of fine-grained tweaking yet.
1953
- // - `mode` SE's gameplay mode hint ('overkill', etc.).
1954
- se_right: !!v.right,
1955
- se_bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
1956
- se_mode: typeof v.mode === "string" ? v.mode : null,
1957
- se_showName: v.showName !== false
1983
+ // Source-side knobs preserved as provenance for future passes.
1984
+ // None have direct Lumia analogs today:
1985
+ // - `right` anchors the boss to the right edge of its
1986
+ // bounds; Lumia uses layer position instead.
1987
+ // - `bossNamePosition` SE-controlled vertical name position; our
1988
+ // themes don't expose that level of tweaking.
1989
+ // - `mode` source-side gameplay mode hint ('overkill').
1990
+ importMeta: buildImportMeta(widget, {
1991
+ right: !!v.right,
1992
+ bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
1993
+ mode: typeof v.mode === "string" ? v.mode : null,
1994
+ showName: v.showName !== false
1995
+ })
1958
1996
  },
1959
1997
  css: {
1960
1998
  background: "transparent"
@@ -6781,6 +6819,7 @@ function extractErrorMessage(err) {
6781
6819
  }
6782
6820
 
6783
6821
  // src/se-import/index.ts
6822
+ var IMPORT_META_KEY = "importMeta";
6784
6823
  var REVIEW_REASONS = {
6785
6824
  "se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
6786
6825
  // `se-widget-hype-cup` intentionally omitted — it now routes to the native
@@ -6927,7 +6966,14 @@ function importSEBootstrap(bootstrap) {
6927
6966
  lights: [],
6928
6967
  css: {},
6929
6968
  content: {
6930
- se_provenance: { widget: w, reason: "Imported as native group", status: "group" }
6969
+ [IMPORT_META_KEY]: {
6970
+ source: "streamelements",
6971
+ widgetType: w.type,
6972
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
6973
+ widget: w,
6974
+ reason: "Imported as native group",
6975
+ status: "group"
6976
+ }
6931
6977
  },
6932
6978
  events: {},
6933
6979
  variables: {}
@@ -6950,7 +6996,10 @@ function importSEBootstrap(bootstrap) {
6950
6996
  if (result.status === "placeholder" || result.status === "template") {
6951
6997
  module.content = {
6952
6998
  ...module.content ?? {},
6953
- se_provenance: {
6999
+ [IMPORT_META_KEY]: {
7000
+ source: "streamelements",
7001
+ widgetType: w.type,
7002
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
6954
7003
  widget: w,
6955
7004
  reason: reasonFor(w.type, result.status, result.flaggedOff),
6956
7005
  status: result.status,
@@ -7031,7 +7080,7 @@ function applyReviewAction(result, moduleId, action, payload) {
7031
7080
  bounds: existingLayer.bounds,
7032
7081
  state: existingLayer.state
7033
7082
  };
7034
- const provenance2 = existing2.content?.se_provenance;
7083
+ const importMeta2 = existing2.content?.[IMPORT_META_KEY];
7035
7084
  const mergedLights = (existing2.lights?.length ?? 0) > 0 ? existing2.lights : transplant.module.lights;
7036
7085
  const mergedEvents = existing2.events && Object.keys(existing2.events).length > 0 ? { ...transplant.module.events, ...existing2.events } : transplant.module.events;
7037
7086
  const mergedVariables = existing2.variables && Object.keys(existing2.variables).length > 0 ? { ...transplant.module.variables, ...existing2.variables } : transplant.module.variables;
@@ -7041,7 +7090,7 @@ function applyReviewAction(result, moduleId, action, payload) {
7041
7090
  lights: mergedLights,
7042
7091
  events: mergedEvents,
7043
7092
  variables: mergedVariables,
7044
- content: { ...transplant.module.content ?? {}, se_provenance: provenance2 }
7093
+ content: { ...transplant.module.content ?? {}, [IMPORT_META_KEY]: importMeta2 }
7045
7094
  };
7046
7095
  const rootOldNewId = transplant.layer.id;
7047
7096
  const remappedExtras = (transplant.extras ?? []).map((e) => ({
@@ -7068,7 +7117,7 @@ function applyReviewAction(result, moduleId, action, payload) {
7068
7117
  const existing = modules[moduleId];
7069
7118
  const generated = payload;
7070
7119
  if (!existing || !generated) return result;
7071
- const provenance = existing.content?.se_provenance;
7120
+ const importMeta = existing.content?.[IMPORT_META_KEY];
7072
7121
  const replaced = {
7073
7122
  ...existing,
7074
7123
  settings: { ...existing.settings ?? {}, type: "custom" },
@@ -7082,7 +7131,7 @@ function applyReviewAction(result, moduleId, action, payload) {
7082
7131
  data: parseLooseJson(generated.data) ?? {},
7083
7132
  configs: parseLooseJson(generated.configs) ?? [],
7084
7133
  flavor: "ai-generated",
7085
- se_provenance: provenance
7134
+ [IMPORT_META_KEY]: importMeta
7086
7135
  }
7087
7136
  };
7088
7137
  modules[moduleId] = replaced;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.2.8-alpha.17",
3
+ "version": "0.2.8-alpha.18",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",