pulse-rb 1.4.1 → 1.4.3

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.
@@ -2,23 +2,36 @@
2
2
  -- Wind UI adapter for Pulse components.
3
3
  -- Docs: footagesus.github.io/treehub-web/docs/windui
4
4
  -- Version: 1.6.64-fix (pinned)
5
-
6
- -- Executor loadstring() environments are sandboxed — each chunk has its OWN environment
7
- -- table, separate from _G. Global reads in this chunk go through getfenv(1), not _G.
8
- -- The compiler passes (_P, _PULSE_LAYOUT) as arguments.
9
- local _P, _PULSE_LAYOUT = ...
10
- if type(_P) == "table" then
5
+ --
6
+ -- Supports two load modes:
7
+ -- 1. Combined (rb publish): inlined into pulse.lua _PULSE_LAYOUT, Components, _Pages, Pulse
8
+ -- are already in scope as upvalues. publish.ts strips everything above [PULSE_INLINE_START].
9
+ -- 2. Separate (legacy): loaded via its own loadstring(_P, _PULSE_LAYOUT) call.
10
+ -- _P carries the bundle table; we spread it into the local env.
11
+ local _isSeparate = type(select(1,...)) == "table"
12
+ if _isSeparate then
13
+ local _P = select(1,...)
14
+ local _PULSE_LAYOUT = select(2,...) or {}
11
15
  local _env = (getfenv and getfenv(1)) or _ENV or _G
12
16
  for _k, _v in pairs(_P) do _env[_k] = _v end
13
17
  end
14
18
  _PULSE_LAYOUT = _PULSE_LAYOUT or {}
15
19
 
16
- -- Capture bundle table references as upvalues immediately so the startup block
17
- -- can access them without global lookups (which are environment-table-dependent
18
- -- in executor sandboxes and may fail in task.spawn callbacks).
19
- local _BundleComponents = type(_P) == "table" and _P.Components or {}
20
- local _BundlePages = type(_P) == "table" and _P._Pages or {}
21
- local _BundlePulse = type(_P) == "table" and _P.Pulse or Pulse
20
+ -- [PULSE_INLINE_START]
21
+ -- Everything below this marker is included verbatim when bundled inline.
22
+ -- In combined mode: Components, _Pages, Pulse are direct upvalues from runtime.lua.
23
+ -- In separate mode: they were spread into the local env by the block above.
24
+ local _BundleComponents = Components or {}
25
+ local _BundlePages = _Pages or {}
26
+ local _BundlePulse = Pulse or {}
27
+
28
+ -- Auto-enable dev logging when layout.dev = true.
29
+ -- Runs synchronously (before any task.spawn) so all UI setup logs are captured.
30
+ if _PULSE_LAYOUT.dev and _BundlePulse and _BundlePulse.Log then
31
+ _BundlePulse.Log.enable()
32
+ _BundlePulse.Log.configure({ level = "debug", console = true })
33
+ _BundlePulse.Log.info("dev", "dev build active")
34
+ end
22
35
 
23
36
  local _WINDUI_URL = "https://github.com/Footagesus/WindUI/releases/download/1.6.64-fix/main.lua"
24
37
  local _PULSE_LOGO = "https://pulse-rb.vercel.app/img/logo.svg"
@@ -589,13 +602,12 @@ task.spawn(function()
589
602
  local _execName = "Unknown"
590
603
  local _execVer = ""
591
604
  pcall(function()
592
- if type(rawget(_G, "identifyexecutor")) == "function" then
593
- local ok, n, v = pcall(identifyexecutor)
594
- if ok then _execName = tostring(n or "Unknown"); _execVer = tostring(v or "") end
595
- end
596
- if _execName == "Unknown" and type(rawget(_G, "getexecutorname")) == "function" then
597
- local ok, n = pcall(getexecutorname)
598
- if ok and n then _execName = tostring(n) end
605
+ -- pcall each call directly — avoids rawget missing __index-proxied executor globals
606
+ local ok, n, v = pcall(function() return identifyexecutor() end)
607
+ if ok and n then _execName = tostring(n); _execVer = tostring(v or "") end
608
+ if _execName == "Unknown" then
609
+ local ok2, n2 = pcall(function() return getexecutorname() end)
610
+ if ok2 and n2 then _execName = tostring(n2) end
599
611
  end
600
612
  if _execName == "Unknown" then
601
613
  if rawget(_G, "syn") or rawget(_G, "Synapse") then _execName = "Synapse X"
@@ -1051,12 +1063,6 @@ task.spawn(function()
1051
1063
  })
1052
1064
  end)
1053
1065
 
1054
- -- Auto-scan on inject
1055
- task.spawn(function()
1056
- if not game:IsLoaded() then game.Loaded:Wait() end
1057
- task.wait(2)
1058
- _doScan()
1059
- end)
1060
1066
  end
1061
1067
 
1062
1068
  -- Info section — framework version + signal counts for debugging
@@ -1086,47 +1092,6 @@ task.spawn(function()
1086
1092
  end
1087
1093
  end
1088
1094
 
1089
- -- Heartbeat Monitor section — tick counts per component (left)
1090
- -- Uses Pulse.Monitor which is independent of the log system.
1091
- local _monSect
1092
- pcall(function()
1093
- _monSect = _devLeft:Section({ Title="Heartbeat Monitor", Icon="activity", Box=true, BoxBorder=true, Opened=true })
1094
- end)
1095
- if _monSect then
1096
- local _monPara
1097
- pcall(function()
1098
- _monPara = _monSect:Paragraph({
1099
- Title = "raw / cond (updates 2s)",
1100
- Desc = "raw = every HB fire · cond = when() passed",
1101
- })
1102
- end)
1103
- -- Reads _rawHb/_condHb plain-Lua counters set in runtime._hbBind.
1104
- -- No dependency on Pulse.Monitor — works regardless of log system state.
1105
- task.spawn(function()
1106
- while _windWindow do
1107
- task.wait(2)
1108
- pcall(function()
1109
- local lines = {}
1110
- for cname, comp in pairs(_BundleComponents) do
1111
- if type(comp) == "table" and comp._rawHb ~= nil then
1112
- lines[#lines+1] = cname
1113
- .. " raw=" .. tostring(comp._rawHb)
1114
- .. " cond=" .. tostring(comp._condHb)
1115
- end
1116
- end
1117
- if _monPara then
1118
- if #lines == 0 then
1119
- _monPara:SetDesc("No heartbeat components registered")
1120
- else
1121
- table.sort(lines)
1122
- _monPara:SetDesc(table.concat(lines, "\n"))
1123
- end
1124
- end
1125
- end)
1126
- end
1127
- end)
1128
- end
1129
-
1130
1095
  -- Console section — controls for the Roblox developer console (F9).
1131
1096
  -- "Clear" wipes the output so only your next prints are visible.
1132
1097
  -- "Print Logs" re-dumps all buffered Pulse.Log entries so you can read
@@ -1172,6 +1137,17 @@ task.spawn(function()
1172
1137
  print("─────────────────────────────────────────")
1173
1138
  end
1174
1139
 
1140
+ pcall(function()
1141
+ _consoleSect:Button({
1142
+ Title = "Open Console",
1143
+ Desc = "Show the Roblox developer console (F9)",
1144
+ Callback = function()
1145
+ pcall(function()
1146
+ game:GetService("StarterGui"):SetCore("DevConsoleVisible", true)
1147
+ end)
1148
+ end,
1149
+ })
1150
+ end)
1175
1151
  pcall(function()
1176
1152
  _consoleSect:Button({
1177
1153
  Title = "Clear Console",
@@ -1207,6 +1183,30 @@ task.spawn(function()
1207
1183
  end
1208
1184
  end
1209
1185
 
1186
+ -- Apply defaults and notify ready — runs after all widgets are created so
1187
+ -- Toggles/Options are populated and SetValue calls land correctly.
1188
+ task.spawn(function()
1189
+ task.wait(0.5)
1190
+ for _, d in ipairs(_PULSE_DEFAULTS) do
1191
+ if d.set then pcall(d.set, d.value) end
1192
+ if d.type == "toggle" then
1193
+ pcall(function()
1194
+ local t = Toggles and Toggles[d.id]
1195
+ if t then t:SetValue(d.value) end
1196
+ end)
1197
+ else
1198
+ pcall(function()
1199
+ local t = Options and Options[d.id]
1200
+ if t then t:SetValue(d.value) end
1201
+ end)
1202
+ end
1203
+ task.wait(0.05)
1204
+ end
1205
+ if #_PULSE_DEFAULTS > 0 and _PulseNotify then
1206
+ _PulseNotify((L.title or "Hub") .. " loaded!", 5)
1207
+ end
1208
+ end)
1209
+
1210
1210
  -- Re-select first tab (Home) — Settings is last so Wind UI leaves it active
1211
1211
  task.defer(function()
1212
1212
  if _UIAdapter._firstTab then
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
56
56
  var init_constants = __esm({
57
57
  "src/constants.ts"() {
58
58
  init_cjs_shims();
59
- RB_VERSION = "1.4.1";
59
+ RB_VERSION = "1.4.3";
60
60
  RB_HOME = path.join(os.homedir(), ".rb");
61
61
  path.join(RB_HOME, "bin");
62
62
  IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
@@ -1110,7 +1110,7 @@ function rglob(dir, exts) {
1110
1110
  walk(dir);
1111
1111
  return results;
1112
1112
  }
1113
- var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, DEFAULTS_RUNNER, Compiler;
1113
+ var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, Compiler;
1114
1114
  var init_compiler = __esm({
1115
1115
  "src/compiler.ts"() {
1116
1116
  init_cjs_shims();
@@ -1130,43 +1130,6 @@ if _G.__AOT_R_DESTROY then pcall(_G.__AOT_R_DESTROY) end
1130
1130
  `;
1131
1131
  DESTROY_REGISTRATION = `-- Register this instance so the next re-execution can clean it up.
1132
1132
  _G.__AOT_R_DESTROY = _PulseDestroy
1133
- `;
1134
- DEFAULTS_RUNNER = `task.spawn(function()
1135
- task.wait(0.5)
1136
- for _, d in ipairs(_PULSE_DEFAULTS) do
1137
- local effectiveValue = d.value
1138
- if d.type == "toggle" and Pulse.TestMode and Pulse.TestMode.isActive() then
1139
- if not Pulse.TestMode.isTarget(d.id) then
1140
- effectiveValue = false
1141
- end
1142
- end
1143
- if d.set then pcall(d.set, effectiveValue) end
1144
- if d.type == "toggle" then
1145
- pcall(function()
1146
- local t = Toggles and Toggles[d.id]
1147
- if t then t:SetValue(effectiveValue) end
1148
- end)
1149
- else
1150
- pcall(function()
1151
- local t = Options and Options[d.id]
1152
- if t then t:SetValue(d.value) end
1153
- end)
1154
- end
1155
- task.wait(0.05)
1156
- end
1157
- if #_PULSE_DEFAULTS > 0 then
1158
- local _notifyMsg = "AOT:Resistance Hub ready!"
1159
- if Pulse.TestMode and Pulse.TestMode.isActive() then
1160
- local _tt = Pulse.TestMode.getTargets()
1161
- if #_tt > 0 then
1162
- _notifyMsg = "Test mode: " .. table.concat(_tt, ", ")
1163
- end
1164
- end
1165
- if _PulseNotify then
1166
- _PulseNotify(_notifyMsg, 5)
1167
- end
1168
- end
1169
- end)
1170
1133
  `;
1171
1134
  Compiler = class {
1172
1135
  root;
@@ -1363,14 +1326,8 @@ end)
1363
1326
  if (opts.dev) parts.push(`,dev=true`);
1364
1327
  parts.push(`}
1365
1328
  `);
1366
- parts.push(`local _P=loadstring(game:HttpGet("${base}/bundle.lua"))()
1367
- `);
1368
- if (opts.dev) {
1369
- parts.push(`_PULSE_DEV = true
1329
+ parts.push(`local _P=loadstring(game:HttpGet("${base}/pulse.lua"))(_PULSE_LAYOUT)
1370
1330
  `);
1371
- parts.push(`if _P.Pulse and _P.Pulse.Log then _P.Pulse.Log.enable() end
1372
- `);
1373
- }
1374
1331
  parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
1375
1332
  `);
1376
1333
  parts.push(
@@ -1388,8 +1345,6 @@ end)
1388
1345
  end
1389
1346
  `
1390
1347
  );
1391
- parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))(_P,_PULSE_LAYOUT)
1392
- `);
1393
1348
  parts.push(`local signal,computed,defineComponent,on=_P.signal,_P.computed,_P.defineComponent,_P.on
1394
1349
  `);
1395
1350
  parts.push(`local toggle,slider,dropdown,multidropdown=_P.toggle,_P.slider,_P.dropdown,_P.multidropdown
@@ -1465,16 +1420,6 @@ end
1465
1420
  }
1466
1421
  parts.push("\n");
1467
1422
  }
1468
- if (order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"))) {
1469
- parts.push("-- [generated: defaults runner]\n");
1470
- parts.push(DEFAULTS_RUNNER);
1471
- parts.push("\n");
1472
- }
1473
- if (opts.dev) {
1474
- parts.push(
1475
- '-- [dev: log config]\nif Pulse and Pulse.Log then\n Pulse.Log.configure({ level = "debug", console = true })\n Pulse.Log.info("dev", "dev build active")\nend\n\n'
1476
- );
1477
- }
1478
1423
  parts.push("-- [generated: destroy registration]\n");
1479
1424
  parts.push(DESTROY_REGISTRATION);
1480
1425
  parts.push("\n");
@@ -1614,7 +1559,12 @@ async function obfuscateSource(source, ib2Dir) {
1614
1559
  if (stdout.startsWith("ERR:")) throw new Error(stdout.slice(4).trim());
1615
1560
  if (result.status !== 0) throw new Error(stderr || stdout || `exit ${result.status}`);
1616
1561
  if (!fs.existsSync(outPath)) throw new Error("IronBrew2 CLI produced no output file");
1617
- return fs.readFileSync(outPath, "utf8");
1562
+ let out = fs.readFileSync(outPath, "utf8");
1563
+ if (out.startsWith("--[[")) {
1564
+ const closeIdx = out.indexOf("]]");
1565
+ if (closeIdx !== -1) out = out.slice(closeIdx + 2).replace(/^\r?\n/, "");
1566
+ }
1567
+ return out;
1618
1568
  } finally {
1619
1569
  try {
1620
1570
  __require("fs").unlinkSync(tmpIn);
@@ -2997,7 +2947,9 @@ async function cmdPublish(_args) {
2997
2947
  process.exit(1);
2998
2948
  }
2999
2949
  const bundleParts = [
3000
- `-- Pulse v${RB_VERSION} bundle (runtime + helpers)`,
2950
+ `-- Pulse v${RB_VERSION} (runtime + helpers + UI adapter)`,
2951
+ `-- Single loadstring \u2014 no executor sandbox split between runtime and UI.`,
2952
+ `local _PULSE_LAYOUT = ... or {}`,
3001
2953
  fs.readFileSync(runtimeFile, "utf8").trimEnd()
3002
2954
  ];
3003
2955
  if (fs.existsSync(helpersDir)) {
@@ -3006,21 +2958,22 @@ async function cmdPublish(_args) {
3006
2958
  bundleParts.push(fs.readFileSync(pathe.join(helpersDir, f), "utf8").trimEnd());
3007
2959
  }
3008
2960
  }
2961
+ const winduiPath = pathe.join(ADAPTERS, "windui.lua");
2962
+ if (fs.existsSync(winduiPath)) {
2963
+ const winduiSrc = fs.readFileSync(winduiPath, "utf8");
2964
+ const markerTag = "-- [PULSE_INLINE_START]";
2965
+ const markerIdx = winduiSrc.indexOf(markerTag);
2966
+ const winduiCore = markerIdx >= 0 ? winduiSrc.slice(markerIdx + markerTag.length).trimStart() : winduiSrc;
2967
+ bundleParts.push("-- [windui adapter inline]");
2968
+ bundleParts.push(winduiCore.trimEnd());
2969
+ }
3009
2970
  bundleParts.push(
3010
2971
  "return {signal=signal,computed=computed,defineComponent=defineComponent,on=on,toggle=toggle,slider=slider,dropdown=dropdown,multidropdown=multidropdown,button=button,keybind=keybind,label=label,separator=separator,groupbox=groupbox,definePage=definePage,Pulse=Pulse,Signal=Signal,Computed=Computed,Component=Component,Components=Components,Store=Store,PulseEvent=PulseEvent,_PulseGetChar=_PulseGetChar,_PulseGetHRP=_PulseGetHRP,_PulseGetHumanoid=_PulseGetHumanoid,_PulseGetAlive=_PulseGetAlive,_PulseRS=_PulseRS,_PulseUIS=_PulseUIS,_PulseDestroy=_PulseDestroy,_PULSE_DEFAULTS=_PULSE_DEFAULTS,_Pages=_Pages,_PulseRunHeartbeat=_PulseRunHeartbeat,_PulseRunRenderStepped=_PulseRunRenderStepped,_PulseRunStepped=_PulseRunStepped,_PulseRunCharAdded=_PulseRunCharAdded,_PulseRunCharRemoving=_PulseRunCharRemoving,_PulseRunInputBegan=_PulseRunInputBegan,_PulseRunInputEnded=_PulseRunInputEnded}"
3011
2972
  );
3012
2973
  const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
3013
2974
  const uploads = [
3014
- { remote: `${VERSION_PATH}/bundle.lua`, content: bundleContent }
2975
+ { remote: `${VERSION_PATH}/pulse.lua`, content: bundleContent }
3015
2976
  ];
3016
- for (const adapter of ["windui"]) {
3017
- const p = pathe.join(ADAPTERS, `${adapter}.lua`);
3018
- if (!fs.existsSync(p)) continue;
3019
- uploads.push({
3020
- remote: `${VERSION_PATH}/adapters/${adapter}.lua`,
3021
- content: Buffer.from(fs.readFileSync(p, "utf8"), "utf8")
3022
- });
3023
- }
3024
2977
  pSection(`Uploading to R2 ${gray("(v" + RB_VERSION + " \xB7 " + uploads.length + " files)")}`);
3025
2978
  for (const { remote, content } of uploads) {
3026
2979
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-rb",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "rb CLI — Pulse framework build tool for Roblox script projects",
5
5
  "bin": {
6
6
  "rb": "./bin/rb.js"
package/pulse/runtime.lua CHANGED
@@ -186,17 +186,6 @@ local function Component(name)
186
186
  end)
187
187
  end
188
188
 
189
- -- Respawn listener (bound to connection tracker)
190
- function c:onRespawn(fn)
191
- local key = "__respawn_" .. name
192
- local existing = self._connections[key]
193
- if existing then
194
- pcall(function() existing:Disconnect() end)
195
- end
196
- self._connections[key] = game:GetService("Players").LocalPlayer
197
- .CharacterAdded:Connect(fn)
198
- end
199
-
200
189
  -- Lifecycle
201
190
  function c:mount()
202
191
  if self._mounted then return end
@@ -354,10 +343,11 @@ local function _signal(default)
354
343
  _sigCounter = _sigCounter + 1
355
344
  local s = Signal(default)
356
345
  s._id = ((_currentComponent and _currentComponent._name) or "g") .. "_s" .. _sigCounter
357
- s.set = function(_, v) s:set(v) end
358
- s.toggle = function(_) s:set(not s:get()) end
346
+ local _rawSet = s.set -- capture original before any override to avoid infinite recursion
347
+ s.set = function(_, v) _rawSet(s, v) end
348
+ s.toggle = function(_) _rawSet(s, not s:get()) end
359
349
  s.watch = function(_, fn) return s:onChange(fn) end
360
- s.update = function(_, fn) s:set(fn(s:get())) end
350
+ s.update = function(_, fn) _rawSet(s, fn(s:get())) end
361
351
  return s
362
352
  end
363
353