@jetstart/core 2.0.2 β†’ 2.1.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.
@@ -63,7 +63,7 @@ class HotReloadService {
63
63
  async hotReload(filePath) {
64
64
  const startTime = Date.now();
65
65
  (0, logger_1.log)(`πŸ”₯ Hot reload starting for: ${path.basename(filePath)}`);
66
- // Step 1: Compile Kotlin to .class
66
+ // Compile Kotlin to .class
67
67
  const compileStart = Date.now();
68
68
  const compileResult = await this.kotlinCompiler.compileFile(filePath);
69
69
  const compileTime = Date.now() - compileStart;
@@ -79,7 +79,7 @@ class HotReloadService {
79
79
  };
80
80
  }
81
81
  (0, logger_1.log)(`Compilation completed in ${compileTime}ms (${compileResult.classFiles.length} classes)`);
82
- // Step 2: Generate $Override classes (Phase 2)
82
+ // Generate $Override classes (Phase 2)
83
83
  const overrideDir = path.join(os.tmpdir(), 'jetstart-overrides', Date.now().toString());
84
84
  fs.mkdirSync(overrideDir, { recursive: true });
85
85
  const overrideResult = await this.overrideGenerator.generateOverrides(compileResult.classFiles, filePath, overrideDir);
@@ -106,7 +106,7 @@ class HotReloadService {
106
106
  compileResult.classFiles.push(...allOverrideClassFiles);
107
107
  }
108
108
  }
109
- // Step 3: Convert .class to .dex
109
+ // Convert .class to .dex
110
110
  const dexStart = Date.now();
111
111
  const dexResult = await this.dexGenerator.generateDex(compileResult.classFiles);
112
112
  const dexTime = Date.now() - dexStart;
@@ -106,17 +106,24 @@ class JsCompilerService {
106
106
  const outKlib = path.join(this.workDir, 'compose_stubs.klib');
107
107
  const argFile = path.join(this.workDir, 'stubs-args.txt');
108
108
  fs.writeFileSync(argFile, [
109
- '-ir-output-name', 'compose_stubs',
110
- '-ir-output-dir', this.workDir,
111
- '-libraries', this.stdlibKlib,
112
- '-module-kind', 'es',
113
- '-target', 'es2015',
109
+ '-ir-output-name',
110
+ 'compose_stubs',
111
+ '-ir-output-dir',
112
+ this.workDir,
113
+ '-libraries',
114
+ this.stdlibKlib,
115
+ '-module-kind',
116
+ 'es',
117
+ '-target',
118
+ 'es2015',
114
119
  '-Xir-produce-klib-file',
115
120
  src,
116
121
  ].join('\n'), 'utf8');
117
122
  (0, logger_1.log)('[JsCompiler] Building Compose shims klib (one-time, ~30s)...');
118
123
  const r = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${argFile}`], {
119
- shell: true, encoding: 'utf8', timeout: 120000,
124
+ shell: true,
125
+ encoding: 'utf8',
126
+ timeout: 120000,
120
127
  });
121
128
  if ((r.status === 0 || !r.stderr?.includes('error:')) && fs.existsSync(outKlib)) {
122
129
  this.stubsKlib = outKlib;
@@ -209,7 +216,7 @@ class JsCompilerService {
209
216
  */
210
217
  removeElseBlockIf(text, predicate) {
211
218
  const re = /\}\s*else\s*\{([^{}]|\{[^{}]*\})*\}/gs;
212
- return text.replace(re, (match) => predicate(match) ? '}' : match);
219
+ return text.replace(re, (match) => (predicate(match) ? '}' : match));
213
220
  }
214
221
  /**
215
222
  * Preprocess a Kotlin Compose source file for kotlinc-js compilation.
@@ -221,7 +228,7 @@ class JsCompilerService {
221
228
  */
222
229
  preprocessFile(ktFilePath, runDir) {
223
230
  let text = fs.readFileSync(ktFilePath, 'utf8');
224
- // ── Pass 1: Whole-file transformations ────────────────────────────────────
231
+ // Pass 1: Whole-file transformations
225
232
  // Imports / package
226
233
  text = text.replace(/^import\s+.*/gm, '');
227
234
  text = text.replace(/^package\s+.*/gm, '');
@@ -261,10 +268,14 @@ class JsCompilerService {
261
268
  text = text.replace(/\bval\s+searchResults\b[^\n]*/g, 'val searchResults: List<String> = listOf("Note 1", "Note 2", "Note 3")');
262
269
  // Named M3 colors
263
270
  const colorMap = {
264
- onSurfaceVariant: '"#49454F"', onBackground: '"#1C1B1F"',
265
- outline: '"#79747E"', surfaceVariant: '"#E7E0EC"',
266
- onPrimaryContainer: '"#21005D"', inverseOnSurface: '"#F4EFF4"',
267
- inverseSurface: '"#313033"', tertiaryContainer: '"#FFD8E4"',
271
+ onSurfaceVariant: '"#49454F"',
272
+ onBackground: '"#1C1B1F"',
273
+ outline: '"#79747E"',
274
+ surfaceVariant: '"#E7E0EC"',
275
+ onPrimaryContainer: '"#21005D"',
276
+ inverseOnSurface: '"#F4EFF4"',
277
+ inverseSurface: '"#313033"',
278
+ tertiaryContainer: '"#FFD8E4"',
268
279
  };
269
280
  for (const [k, v] of Object.entries(colorMap)) {
270
281
  text = text.replace(new RegExp(`MaterialTheme\\.colorScheme\\.${k}\\b`, 'g'), v);
@@ -310,7 +321,7 @@ class JsCompilerService {
310
321
  // Fix extra ) from removed args in grid calls
311
322
  text = text.replace(/StaggeredGridCells\.Fixed\((\d+)\)\),/g, 'StaggeredGridCells.Fixed($1),');
312
323
  text = text.replace(/modifier\s*=\s*Modifier\(\)\),/g, 'modifier = Modifier(),');
313
- // ── Pass 2: Brace-balanced block removal ──────────────────────────────────
324
+ // Pass 2: Brace-balanced block removal
314
325
  // LaunchedEffect / SideEffect / DisposableEffect
315
326
  for (const kw of ['LaunchedEffect', 'DisposableEffect', 'SideEffect']) {
316
327
  text = this.removeBalancedBlock(text, new RegExp(`\\b${kw}\\b[^{]*\\{`, 'g'));
@@ -325,13 +336,12 @@ class JsCompilerService {
325
336
  text = this.removeBalancedBlock(text, /if\s*\(\s*\w+\.isNotEmpty\(\)\s*\)\s*\{/g);
326
337
  // Remove else blocks containing broken/removed suggestedTags code
327
338
  text = this.removeElseBlockIf(text, (body) => body.includes('suggestedTags') || body.includes('Modifier()),'));
328
- // ── Pass 3: Final cleanup ─────────────────────────────────────────────────
339
+ // Pass 3: Final cleanup
329
340
  text = text.replace(/\n{3,}/g, '\n\n');
330
341
  text = text.replace(/,\s*\)/g, ')');
331
342
  text = text.replace(/\(\s*,/g, '(');
332
343
  // Detect the screen function name to wrap the entry point
333
- const screenMatch = text.match(/^fun\s+(\w+Screen)/m) ||
334
- text.match(/^fun\s+(\w+)/m);
344
+ const screenMatch = text.match(/^fun\s+(\w+Screen)/m) || text.match(/^fun\s+(\w+)/m);
335
345
  const screenName = screenMatch ? screenMatch[1] : null;
336
346
  const renderWrapper = screenName
337
347
  ? `\n\n@OptIn(kotlin.js.ExperimentalJsExport::class)\n@JsExport\nfun __jetstart_render__(): dynamic = renderScreen { ${screenName}() }\n`
@@ -352,49 +362,69 @@ class JsCompilerService {
352
362
  const runDir = path.join(this.workDir, runId);
353
363
  fs.mkdirSync(runDir, { recursive: true });
354
364
  try {
355
- // ── Step 1: .kt β†’ screen.klib ─────────────────────────────────────────
365
+ // Step 1: .kt β†’ screen.klib
356
366
  const screenKlib = path.join(runDir, 'screen.klib');
357
367
  const args1 = path.join(runDir, 'step1.txt');
358
368
  const libs1 = [this.stdlibKlib, stubsKlib].join(process.platform === 'win32' ? ';' : ':');
359
369
  // Preprocess: strip Android imports, annotations, and types not available in JS
360
370
  const preprocessedKtPath = this.preprocessFile(ktFilePath, runDir);
361
371
  fs.writeFileSync(args1, [
362
- '-ir-output-name', 'screen',
363
- '-ir-output-dir', runDir,
364
- '-libraries', libs1,
365
- '-module-kind', 'es',
366
- '-target', 'es2015',
372
+ '-ir-output-name',
373
+ 'screen',
374
+ '-ir-output-dir',
375
+ runDir,
376
+ '-libraries',
377
+ libs1,
378
+ '-module-kind',
379
+ 'es',
380
+ '-target',
381
+ 'es2015',
367
382
  '-Xir-produce-klib-file',
368
383
  preprocessedKtPath,
369
384
  ].join('\n'), 'utf8');
370
385
  const r1 = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${args1}`], {
371
- shell: true, encoding: 'utf8', timeout: 120000,
386
+ shell: true,
387
+ encoding: 'utf8',
388
+ timeout: 120000,
372
389
  });
373
390
  if (!fs.existsSync(screenKlib)) {
374
- const errLines = (r1.stderr || r1.stdout || '').split('\n')
375
- .filter((l) => l.includes('error:')).slice(0, 8).join('\n');
391
+ const errLines = (r1.stderr || r1.stdout || '')
392
+ .split('\n')
393
+ .filter((l) => l.includes('error:'))
394
+ .slice(0, 8)
395
+ .join('\n');
376
396
  return { success: false, error: `Step 1 failed:\n${errLines}` };
377
397
  }
378
- // ── Step 2: screen.klib β†’ screen.mjs ──────────────────────────────────
398
+ // Step 2: screen.klib β†’ screen.mjs
379
399
  const outDir = path.join(runDir, 'out');
380
400
  fs.mkdirSync(outDir, { recursive: true });
381
401
  const args2 = path.join(runDir, 'step2.txt');
382
402
  fs.writeFileSync(args2, [
383
- '-ir-output-name', 'screen',
384
- '-ir-output-dir', outDir,
385
- '-libraries', libs1,
386
- '-module-kind', 'es',
387
- '-target', 'es2015',
403
+ '-ir-output-name',
404
+ 'screen',
405
+ '-ir-output-dir',
406
+ outDir,
407
+ '-libraries',
408
+ libs1,
409
+ '-module-kind',
410
+ 'es',
411
+ '-target',
412
+ 'es2015',
388
413
  '-Xir-produce-js',
389
414
  `-Xinclude=${screenKlib}`,
390
415
  ].join('\n'), 'utf8');
391
416
  const r2 = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${args2}`], {
392
- shell: true, encoding: 'utf8', timeout: 120000,
417
+ shell: true,
418
+ encoding: 'utf8',
419
+ timeout: 120000,
393
420
  });
394
421
  const mjsPath = path.join(outDir, 'screen.mjs');
395
422
  if (!fs.existsSync(mjsPath)) {
396
- const errLines = (r2.stderr || r2.stdout || '').split('\n')
397
- .filter((l) => !l.startsWith('warning:') && l.trim()).slice(0, 8).join('\n');
423
+ const errLines = (r2.stderr || r2.stdout || '')
424
+ .split('\n')
425
+ .filter((l) => !l.startsWith('warning:') && l.trim())
426
+ .slice(0, 8)
427
+ .join('\n');
398
428
  return { success: false, error: `Step 2 failed:\n${errLines}` };
399
429
  }
400
430
  // Detect the screen function name from source
@@ -405,7 +435,13 @@ class JsCompilerService {
405
435
  const jsBase64 = mjsBytes.toString('base64');
406
436
  const elapsed = Date.now() - t0;
407
437
  (0, logger_1.log)(`[JsCompiler] βœ… ${path.basename(ktFilePath)} β†’ JS in ${elapsed}ms (${mjsBytes.length} bytes)`);
408
- return { success: true, jsBase64, byteSize: mjsBytes.length, compileTimeMs: elapsed, screenFunctionName };
438
+ return {
439
+ success: true,
440
+ jsBase64,
441
+ byteSize: mjsBytes.length,
442
+ compileTimeMs: elapsed,
443
+ screenFunctionName,
444
+ };
409
445
  }
410
446
  finally {
411
447
  try {
@@ -416,6 +452,6 @@ class JsCompilerService {
416
452
  }
417
453
  }
418
454
  exports.JsCompilerService = JsCompilerService;
419
- // ── Compose shim stubs (validated against kotlinc-js 2.3.0) ──────────────────
420
- const COMPOSE_STUBS = "package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js(\"({type:'root',children:[]})\")\n val stack: dynamic = js(\"([])\")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β€” prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js(\"({type:'slot',children:[]})\")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js(\"({})\")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = \"top\"; val Bottom = \"bottom\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val SpaceBetween = \"space-between\"; val SpaceEvenly = \"space-evenly\"; val SpaceAround = \"space-around\"\n fun spacedBy(dp: Int) = \"spacedBy(${\"$\"}dp)\"\n fun spacedBy(dp: Float) = \"spacedBy(${\"$\"}{dp.toInt()})\"\n}\nobject Alignment {\n val Top = \"top\"; val Bottom = \"bottom\"; val CenterVertically = \"centerVertically\"\n val CenterHorizontally = \"centerHorizontally\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val TopStart = \"topStart\"; val TopEnd = \"topEnd\"\n val BottomStart = \"bottomStart\"; val BottomEnd = \"bottomEnd\"\n val TopCenter = \"topCenter\"; val BottomCenter = \"bottomCenter\"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = \"#6750A4\"; val onPrimary = \"#FFFFFF\"\n val secondary = \"#625B71\"; val surface = \"#FFFBFE\"\n val background = \"#FFFBFE\"; val error = \"#B3261E\"\n val primaryContainer = \"#EADDFF\"; val secondaryContainer = \"#E8DEF8\"\n val surfaceVariant = \"#E7E0EC\"; val outline = \"#79747E\"\n val onBackground = \"#1C1B1F\"; val onSurface = \"#1C1B1F\"\n val tertiaryContainer = \"#FFD8E4\"; val inverseSurface = \"#313033\"\n val inverseOnSurface = \"#F4EFF4\"; val onPrimaryContainer = \"#21005D\"\n}\nclass Typography {\n val displayLarge = \"displayLarge\"; val displayMedium = \"displayMedium\"; val displaySmall = \"displaySmall\"\n val headlineLarge = \"headlineLarge\"; val headlineMedium = \"headlineMedium\"; val headlineSmall = \"headlineSmall\"\n val titleLarge = \"titleLarge\"; val titleMedium = \"titleMedium\"; val titleSmall = \"titleSmall\"\n val bodyLarge = \"bodyLarge\"; val bodyMedium = \"bodyMedium\"; val bodySmall = \"bodySmall\"\n val labelLarge = \"labelLarge\"; val labelMedium = \"labelMedium\"; val labelSmall = \"labelSmall\"\n}\nclass Shapes { val small=\"small\"; val medium=\"medium\"; val large=\"large\"; val extraLarge=\"extraLarge\" }\nobject FontWeight { val Bold=\"bold\"; val Normal=\"normal\"; val Light=\"light\"; val Medium=\"medium\"; val SemiBold=\"semibold\" }\nobject TextAlign { val Start=\"start\"; val Center=\"center\"; val End=\"end\"; val Justify=\"justify\" }\nobject ContentScale { val Crop=\"crop\"; val Fit=\"fit\"; val FillBounds=\"fillBounds\"; val FillWidth=\"fillWidth\" }\nobject Color {\n val White = \"#FFFFFF\"; val Black = \"#000000\"; val Transparent = \"transparent\"\n val Red = \"#F44336\"; val Blue = \"#2196F3\"; val Green = \"#4CAF50\"\n val Gray = \"#9E9E9E\"; val LightGray = \"#E0E0E0\"; val DarkGray = \"#424242\"\n val Unspecified = \"unspecified\"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = \"string_${\"$\"}id\"\nfun painterResource(id: Int): Any = js(\"({type:'resource',id:${\"$\"}id})\")\n\nobject Icons {\n object Default {\n val Add: Any = js(\"({icon:'add'})\")\n val Search: Any = js(\"({icon:'search'})\")\n val Close: Any = js(\"({icon:'close'})\")\n val Check: Any = js(\"({icon:'check'})\")\n val Delete: Any = js(\"({icon:'delete'})\")\n val Edit: Any = js(\"({icon:'edit'})\")\n val Home: Any = js(\"({icon:'home'})\")\n val Menu: Any = js(\"({icon:'menu'})\")\n val Settings: Any = js(\"({icon:'settings'})\")\n val ArrowBack: Any = js(\"({icon:'arrow_back'})\")\n val MoreVert: Any = js(\"({icon:'more_vert'})\")\n val Favorite: Any = js(\"({icon:'favorite'})\")\n val Share: Any = js(\"({icon:'share'})\")\n val Info: Any = js(\"({icon:'info'})\")\n val Person: Any = js(\"({icon:'person'})\")\n val Star: Any = js(\"({icon:'star'})\")\n val Notifications: Any = js(\"({icon:'notifications'})\")\n val Email: Any = js(\"({icon:'email'})\")\n val Phone: Any = js(\"({icon:'phone'})\")\n val Lock: Any = js(\"({icon:'lock'})\")\n val Visibility: Any = js(\"({icon:'visibility'})\")\n val VisibilityOff: Any = js(\"({icon:'visibility_off'})\")\n val ShoppingCart: Any = js(\"({icon:'shopping_cart'})\")\n val KeyboardArrowDown: Any = js(\"({icon:'keyboard_arrow_down'})\")\n val KeyboardArrowUp: Any = js(\"({icon:'keyboard_arrow_up'})\")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ── Layout composables ────────────────────────────────────────────────────────\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any=\"\", horizontalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Column',children:[]})\")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any=\"\", verticalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Row',children:[]})\")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any=\"topStart\", content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'Box',children:[]})\")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any=\"\", color: Any=\"\", fontSize: Any=\"\", fontWeight: Any=\"\", textAlign: Any=\"\", maxLines: Int=Int.MAX_VALUE, overflow: Any=\"\") {\n val n: dynamic = js(\"({type:'Text',children:[]})\")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) \"bodyMedium\" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any=\"\", colors: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Button',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'TextButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'FilledTonalButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'FloatingActionButton',children:[]})\")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js(\"({type:'ExtendedFAB',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'IconButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", colors: Any=\"\", elevation: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Card',children:[]})\")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any=\"\", content: (Any)->Unit) {\n val n: dynamic = js(\"({type:'Scaffold',children:[],topBar:[],bottomBar:[],fab:[]})\")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js(\"({})\")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") {\n val n: dynamic = js(\"({type:'TopAppBar',children:[],title:[],actions:[]})\")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'NavigationBar',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'NavigationBarItem',children:[],label:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'OutlinedTextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'TextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'SearchBar',children:[]})\")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", verticalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyColumn',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyRow',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", verticalArrangement: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalStaggeredGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any=\"\") {\n val n: dynamic = js(\"({type:'Icon',children:[]})\")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else \"default\"\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js(\"({type:'Image',children:[]})\")\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'Spacer',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") {\n val n: dynamic = js(\"({type:'Divider',children:[]})\")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Switch',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Checkbox',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any=\"\", strokeWidth: Int=4) {\n val n: dynamic = js(\"({type:'CircularProgressIndicator',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any=\"\") {\n val n: dynamic = js(\"({type:'LinearProgressIndicator',children:[]})\")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'assist',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'filter',children:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'suggestion',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'AlertDialog',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})\")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js(\"({type:'DropdownMenu',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js(\"({type:'DropdownMenuItem',children:[]})\")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js(\"({})\")\nfun rememberScrollState(): Any = js(\"({})\")\nfun SnackbarHostState(): Any = js(\"({})\")\nfun rememberSnackbarHostState(): Any = js(\"({})\")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js(\"({})\")\nobject KeyboardOptions { val Default = js(\"({})\") }\nobject KeyboardActions { val Default = js(\"({})\") }\nobject ImeAction { val Done=\"done\"; val Search=\"search\"; val Go=\"go\"; val Next=\"next\" }\nobject KeyboardType { val Text=\"text\"; val Number=\"number\"; val Email=\"email\"; val Password=\"password\" }\nfun PaddingValues(all: Int=0): Any = js(\"({})\")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js(\"({})\")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js(\"({})\")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ── Entry point called by the browser ────────────────────────────────────────\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}";
455
+ // Compose shim stubs (validated against kotlinc-js 2.3.0)
456
+ const COMPOSE_STUBS = 'package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js("({type:\'root\',children:[]})")\n val stack: dynamic = js("([])")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β€” prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js("({type:\'slot\',children:[]})")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js("({})")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = "top"; val Bottom = "bottom"; val Center = "center"\n val Start = "start"; val End = "end"\n val SpaceBetween = "space-between"; val SpaceEvenly = "space-evenly"; val SpaceAround = "space-around"\n fun spacedBy(dp: Int) = "spacedBy(${"$"}dp)"\n fun spacedBy(dp: Float) = "spacedBy(${"$"}{dp.toInt()})"\n}\nobject Alignment {\n val Top = "top"; val Bottom = "bottom"; val CenterVertically = "centerVertically"\n val CenterHorizontally = "centerHorizontally"; val Center = "center"\n val Start = "start"; val End = "end"\n val TopStart = "topStart"; val TopEnd = "topEnd"\n val BottomStart = "bottomStart"; val BottomEnd = "bottomEnd"\n val TopCenter = "topCenter"; val BottomCenter = "bottomCenter"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = "#6750A4"; val onPrimary = "#FFFFFF"\n val secondary = "#625B71"; val surface = "#FFFBFE"\n val background = "#FFFBFE"; val error = "#B3261E"\n val primaryContainer = "#EADDFF"; val secondaryContainer = "#E8DEF8"\n val surfaceVariant = "#E7E0EC"; val outline = "#79747E"\n val onBackground = "#1C1B1F"; val onSurface = "#1C1B1F"\n val tertiaryContainer = "#FFD8E4"; val inverseSurface = "#313033"\n val inverseOnSurface = "#F4EFF4"; val onPrimaryContainer = "#21005D"\n}\nclass Typography {\n val displayLarge = "displayLarge"; val displayMedium = "displayMedium"; val displaySmall = "displaySmall"\n val headlineLarge = "headlineLarge"; val headlineMedium = "headlineMedium"; val headlineSmall = "headlineSmall"\n val titleLarge = "titleLarge"; val titleMedium = "titleMedium"; val titleSmall = "titleSmall"\n val bodyLarge = "bodyLarge"; val bodyMedium = "bodyMedium"; val bodySmall = "bodySmall"\n val labelLarge = "labelLarge"; val labelMedium = "labelMedium"; val labelSmall = "labelSmall"\n}\nclass Shapes { val small="small"; val medium="medium"; val large="large"; val extraLarge="extraLarge" }\nobject FontWeight { val Bold="bold"; val Normal="normal"; val Light="light"; val Medium="medium"; val SemiBold="semibold" }\nobject TextAlign { val Start="start"; val Center="center"; val End="end"; val Justify="justify" }\nobject ContentScale { val Crop="crop"; val Fit="fit"; val FillBounds="fillBounds"; val FillWidth="fillWidth" }\nobject Color {\n val White = "#FFFFFF"; val Black = "#000000"; val Transparent = "transparent"\n val Red = "#F44336"; val Blue = "#2196F3"; val Green = "#4CAF50"\n val Gray = "#9E9E9E"; val LightGray = "#E0E0E0"; val DarkGray = "#424242"\n val Unspecified = "unspecified"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = "string_${"$"}id"\nfun painterResource(id: Int): Any = js("({type:\'resource\',id:${"$"}id})")\n\nobject Icons {\n object Default {\n val Add: Any = js("({icon:\'add\'})")\n val Search: Any = js("({icon:\'search\'})")\n val Close: Any = js("({icon:\'close\'})")\n val Check: Any = js("({icon:\'check\'})")\n val Delete: Any = js("({icon:\'delete\'})")\n val Edit: Any = js("({icon:\'edit\'})")\n val Home: Any = js("({icon:\'home\'})")\n val Menu: Any = js("({icon:\'menu\'})")\n val Settings: Any = js("({icon:\'settings\'})")\n val ArrowBack: Any = js("({icon:\'arrow_back\'})")\n val MoreVert: Any = js("({icon:\'more_vert\'})")\n val Favorite: Any = js("({icon:\'favorite\'})")\n val Share: Any = js("({icon:\'share\'})")\n val Info: Any = js("({icon:\'info\'})")\n val Person: Any = js("({icon:\'person\'})")\n val Star: Any = js("({icon:\'star\'})")\n val Notifications: Any = js("({icon:\'notifications\'})")\n val Email: Any = js("({icon:\'email\'})")\n val Phone: Any = js("({icon:\'phone\'})")\n val Lock: Any = js("({icon:\'lock\'})")\n val Visibility: Any = js("({icon:\'visibility\'})")\n val VisibilityOff: Any = js("({icon:\'visibility_off\'})")\n val ShoppingCart: Any = js("({icon:\'shopping_cart\'})")\n val KeyboardArrowDown: Any = js("({icon:\'keyboard_arrow_down\'})")\n val KeyboardArrowUp: Any = js("({icon:\'keyboard_arrow_up\'})")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ── Layout composables ────────────────────────────────────────────────────────\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any="", horizontalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Column\',children:[]})")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any="", verticalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Row\',children:[]})")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any="topStart", content: ()->Unit={}) {\n val n: dynamic = js("({type:\'Box\',children:[]})")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any="", color: Any="", fontSize: Any="", fontWeight: Any="", textAlign: Any="", maxLines: Int=Int.MAX_VALUE, overflow: Any="") {\n val n: dynamic = js("({type:\'Text\',children:[]})")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) "bodyMedium" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any="", colors: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Button\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'TextButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'FilledTonalButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'FloatingActionButton\',children:[]})")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js("({type:\'ExtendedFAB\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'IconButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", colors: Any="", elevation: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Card\',children:[]})")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any="", content: (Any)->Unit) {\n val n: dynamic = js("({type:\'Scaffold\',children:[],topBar:[],bottomBar:[],fab:[]})")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js("({})")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") {\n val n: dynamic = js("({type:\'TopAppBar\',children:[],title:[],actions:[]})")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'NavigationBar\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'NavigationBarItem\',children:[],label:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'OutlinedTextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'TextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js("({type:\'SearchBar\',children:[]})")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", verticalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyColumn\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyRow\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", verticalArrangement: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalStaggeredGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any="") {\n val n: dynamic = js("({type:\'Icon\',children:[]})")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else "default"\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js("({type:\'Image\',children:[]})")\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'Spacer\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") {\n val n: dynamic = js("({type:\'Divider\',children:[]})")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Switch\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Checkbox\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any="", strokeWidth: Int=4) {\n val n: dynamic = js("({type:\'CircularProgressIndicator\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any="") {\n val n: dynamic = js("({type:\'LinearProgressIndicator\',children:[]})")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'assist\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'filter\',children:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'suggestion\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'AlertDialog\',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js("({type:\'DropdownMenu\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js("({type:\'DropdownMenuItem\',children:[]})")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js("({})")\nfun rememberScrollState(): Any = js("({})")\nfun SnackbarHostState(): Any = js("({})")\nfun rememberSnackbarHostState(): Any = js("({})")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js("({})")\nobject KeyboardOptions { val Default = js("({})") }\nobject KeyboardActions { val Default = js("({})") }\nobject ImeAction { val Done="done"; val Search="search"; val Go="go"; val Next="next" }\nobject KeyboardType { val Text="text"; val Number="number"; val Email="email"; val Password="password" }\nfun PaddingValues(all: Int=0): Any = js("({})")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js("({})")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js("({})")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ── Entry point called by the browser ────────────────────────────────────────\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}';
421
457
  //# sourceMappingURL=js-compiler-service.js.map
@@ -2,6 +2,7 @@
2
2
  * Main Server Entry Point
3
3
  * Starts HTTP and WebSocket servers
4
4
  */
5
+ import 'dotenv/config';
5
6
  import { EventEmitter } from 'events';
6
7
  import { ServerSession } from '../types';
7
8
  export interface ServerConfig {
@@ -1,8 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Main Server Entry Point
4
- * Starts HTTP and WebSocket servers
5
- */
6
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
3
  if (k2 === undefined) k2 = k;
8
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -38,6 +34,11 @@ var __importStar = (this && this.__importStar) || (function () {
38
34
  })();
39
35
  Object.defineProperty(exports, "__esModule", { value: true });
40
36
  exports.JetStartServer = void 0;
37
+ /**
38
+ * Main Server Entry Point
39
+ * Starts HTTP and WebSocket servers
40
+ */
41
+ require("dotenv/config");
41
42
  const path = __importStar(require("path"));
42
43
  const fs = __importStar(require("fs"));
43
44
  const os = __importStar(require("os"));
@@ -148,8 +149,8 @@ class JetStartServer extends events_1.EventEmitter {
148
149
  port: this.config.wsPort,
149
150
  logsServer: this.logsServer,
150
151
  adbHelper: this.adbHelper, // Enable wireless ADB auto-connect
151
- expectedSessionId: this.currentSession?.id, // Reject old-session devices
152
- expectedToken: this.currentSession?.token, // Validate token too
152
+ expectedSessionId: this.currentSession?.id,
153
+ expectedToken: this.currentSession?.token,
153
154
  projectName: this.config.projectName,
154
155
  onClientConnected: async (sessionId) => {
155
156
  (0, logger_1.log)(`Client connected (session: ${sessionId}). Triggering initial build...`);
@@ -243,7 +244,7 @@ class JetStartServer extends events_1.EventEmitter {
243
244
  if (this.wsHandler && this.currentSession) {
244
245
  // Store the APK path
245
246
  this.latestApkPath = result.apkPath || null;
246
- // Send full download URL to client (not just relative path)
247
+ // Send full download URL to client
247
248
  const downloadUrl = `http://${this.config.displayHost}:${this.config.httpPort}/download/app.apk`;
248
249
  this.wsHandler.sendBuildComplete(this.currentSession.id, downloadUrl);
249
250
  (0, logger_1.log)(`APK download URL: ${downloadUrl}`);
@@ -265,7 +266,9 @@ class JetStartServer extends events_1.EventEmitter {
265
266
  appPackageName = cfg.packageName || appPackageName;
266
267
  }
267
268
  }
268
- catch (_) { /* empty */ }
269
+ catch (error) {
270
+ console.log("couldnt find package name");
271
+ }
269
272
  await this.adbHelper.launchApp(appPackageName, '.MainActivity');
270
273
  }
271
274
  else {
@@ -320,7 +323,7 @@ class JetStartServer extends events_1.EventEmitter {
320
323
  const result = await this.buildService.build({
321
324
  projectPath: this.config.projectPath,
322
325
  outputPath: path.join(this.config.projectPath, 'build/outputs/apk'),
323
- buildType: 'debug', // BuildType.DEBUG
326
+ buildType: 'debug',
324
327
  minifyEnabled: false,
325
328
  debuggable: true,
326
329
  versionCode: 1,
@@ -13,8 +13,35 @@ const cors_1 = __importDefault(require("cors"));
13
13
  const logger_1 = require("../utils/logger");
14
14
  function setupMiddleware(app) {
15
15
  // CORS
16
+ const isAllowedOrigin = (origin) => {
17
+ // Standard allowed origins
18
+ const allowedOrigins = [
19
+ 'http://localhost:8000',
20
+ 'http://localhost:3000',
21
+ 'http://localhost:8765',
22
+ 'http://localhost:8766',
23
+ 'http://localhost:8767',
24
+ ...(process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : [])
25
+ ];
26
+ if (allowedOrigins.indexOf(origin) !== -1)
27
+ return true;
28
+ // Allow jetstart.site and any subdomains
29
+ if (origin === 'https://jetstart.site' || origin.endsWith('.jetstart.site'))
30
+ return true;
31
+ return false;
32
+ };
16
33
  app.use((0, cors_1.default)({
17
- origin: '*',
34
+ origin: (origin, callback) => {
35
+ // Allow requests with no origin (like mobile apps or curl requests)
36
+ if (!origin)
37
+ return callback(null, true);
38
+ if (isAllowedOrigin(origin) || !process.env.IS_PROD) {
39
+ callback(null, true);
40
+ }
41
+ else {
42
+ callback(null, false);
43
+ }
44
+ },
18
45
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
19
46
  allowedHeaders: ['Content-Type', 'Authorization'],
20
47
  }));
@@ -1,8 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * HTTP Routes
4
- * REST API endpoints
5
- */
6
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
4
  };
@@ -16,6 +12,7 @@ const sessionManager = new session_1.SessionManager();
16
12
  function setupRoutes(app, getLatestApk, getCurrentSession) {
17
13
  // Root Redirect -> Web Emulator
18
14
  app.get('/', (req, res) => {
15
+ const isProduction = process.env.IS_PROD;
19
16
  try {
20
17
  const session = getCurrentSession?.();
21
18
  // If no session active, just show simple status
@@ -26,7 +23,7 @@ function setupRoutes(app, getLatestApk, getCurrentSession) {
26
23
  `);
27
24
  }
28
25
  // Construct redirect URL
29
- const webUrl = 'https://web.jetstart.site';
26
+ const webUrl = isProduction ? 'https://web.jetstart.site' : 'http://localhost:8000';
30
27
  const host = req.hostname;
31
28
  const port = req.socket.localPort || 8765;
32
29
  const wsPort = shared_1.DEFAULT_WS_PORT;
@@ -39,7 +39,7 @@ export declare class WebSocketHandler {
39
39
  * Send compiled Kotlinβ†’JS ES module to web emulator clients.
40
40
  * The browser imports it dynamically and renders the Compose UI as HTML.
41
41
  */
42
- sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number, screenFunctionName: string): void;
42
+ sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number, _screenFunctionName: string): void;
43
43
  sendLogBroadcast(sessionId: string, logEntry: any): void;
44
44
  }
45
45
  //# sourceMappingURL=handler.d.ts.map
@@ -76,13 +76,13 @@ class WebSocketHandler {
76
76
  handleConnect(clientId, message) {
77
77
  const incomingSession = message.sessionId;
78
78
  const incomingToken = message.token;
79
- // ── Session + token validation ──────────────────────────────────────────
79
+ // Session + token validation
80
80
  // Only validate when the server has an expected session configured.
81
81
  if (this.expectedSessionId) {
82
82
  if (incomingSession !== this.expectedSessionId) {
83
83
  (0, logger_1.error)(`Rejected client ${clientId}: wrong session "${incomingSession}" (expected "${this.expectedSessionId}")`);
84
84
  (0, logger_1.error)('This device was built against a different jetstart dev session. Rescan the QR code.');
85
- // Close the WebSocket immediately β€” do not accept this client.
85
+ // Close the WebSocket immediately β€” does not accept this client.
86
86
  const ws = this.connectionManager.getConnection(clientId);
87
87
  if (ws) {
88
88
  ws.close(4001, 'Session mismatch β€” rescan QR code');
@@ -100,7 +100,7 @@ class WebSocketHandler {
100
100
  return;
101
101
  }
102
102
  }
103
- // ── Accepted ─────────────────────────────────────────────────────────────
103
+ // Accepted
104
104
  (0, logger_1.log)(`Client accepted (session: ${incomingSession})`);
105
105
  this.connectionManager.setClientSession(clientId, incomingSession);
106
106
  const response = {
@@ -160,7 +160,7 @@ class WebSocketHandler {
160
160
  * Send compiled Kotlinβ†’JS ES module to web emulator clients.
161
161
  * The browser imports it dynamically and renders the Compose UI as HTML.
162
162
  */
163
- sendJsUpdate(sessionId, jsBase64, sourceFile, byteSize, screenFunctionName) {
163
+ sendJsUpdate(sessionId, jsBase64, sourceFile, byteSize, _screenFunctionName) {
164
164
  const message = {
165
165
  type: 'core:js-update',
166
166
  timestamp: Date.now(),
@@ -14,11 +14,6 @@ export declare class ConnectionManager {
14
14
  */
15
15
  setClientSession(clientId: string, sessionId: string): void;
16
16
  sendToClient(clientId: string, message: CoreMessage): boolean;
17
- /**
18
- * Broadcast to all clients (UNSAFE - use broadcastToSession instead)
19
- * @deprecated Use broadcastToSession for session isolation
20
- */
21
- broadcast(message: CoreMessage): void;
22
17
  /**
23
18
  * Broadcast to all clients in a specific session (SECURE)
24
19
  */
@@ -46,14 +46,6 @@ class ConnectionManager {
46
46
  return false;
47
47
  }
48
48
  }
49
- /**
50
- * Broadcast to all clients (UNSAFE - use broadcastToSession instead)
51
- * @deprecated Use broadcastToSession for session isolation
52
- */
53
- broadcast(message) {
54
- console.warn('[ConnectionManager] WARNING: Using unsafe broadcast() - use broadcastToSession() instead');
55
- this.broadcastToAll(message);
56
- }
57
49
  /**
58
50
  * Broadcast to all clients in a specific session (SECURE)
59
51
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetstart/core",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Build server and orchestration for JetStart",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "homepage": "https://github.com/dev-phantom/jetstart#readme",
35
35
  "dependencies": {
36
- "@jetstart/shared": "^2.0.2",
37
- "@jetstart/logs": "^2.0.2",
36
+ "@jetstart/shared": "^2.1.0",
37
+ "@jetstart/logs": "^2.1.0",
38
38
  "express": "^4.18.2",
39
39
  "ws": "^8.14.2",
40
40
  "chokidar": "^3.5.3",
@@ -42,7 +42,7 @@ export class HotReloadService {
42
42
 
43
43
  log(`πŸ”₯ Hot reload starting for: ${path.basename(filePath)}`);
44
44
 
45
- // Step 1: Compile Kotlin to .class
45
+ // Compile Kotlin to .class
46
46
  const compileStart = Date.now();
47
47
  const compileResult = await this.kotlinCompiler.compileFile(filePath);
48
48
  const compileTime = Date.now() - compileStart;
@@ -61,7 +61,7 @@ export class HotReloadService {
61
61
 
62
62
  log(`Compilation completed in ${compileTime}ms (${compileResult.classFiles.length} classes)`);
63
63
 
64
- // Step 2: Generate $Override classes (Phase 2)
64
+ // Generate $Override classes (Phase 2)
65
65
  const overrideDir = path.join(os.tmpdir(), 'jetstart-overrides', Date.now().toString());
66
66
  fs.mkdirSync(overrideDir, { recursive: true });
67
67
 
@@ -95,7 +95,7 @@ export class HotReloadService {
95
95
  }
96
96
  }
97
97
 
98
- // Step 3: Convert .class to .dex
98
+ // Convert .class to .dex
99
99
  const dexStart = Date.now();
100
100
  const dexResult = await this.dexGenerator.generateDex(compileResult.classFiles);
101
101
  const dexTime = Date.now() - dexStart;
@@ -46,7 +46,10 @@ export class JsCompilerService {
46
46
  path.join(process.env.PATH?.split(isWin ? ';' : ':')[0] || '', `kotlinc-js${ext}`),
47
47
  ];
48
48
  for (const c of candidates) {
49
- if (c && fs.existsSync(c)) { this.kotlincJsPath = c; break; }
49
+ if (c && fs.existsSync(c)) {
50
+ this.kotlincJsPath = c;
51
+ break;
52
+ }
50
53
  }
51
54
  if (!this.kotlincJsPath) {
52
55
  warn('[JsCompiler] kotlinc-js not found β€” web live preview disabled');
@@ -83,19 +86,30 @@ export class JsCompilerService {
83
86
  const outKlib = path.join(this.workDir, 'compose_stubs.klib');
84
87
  const argFile = path.join(this.workDir, 'stubs-args.txt');
85
88
 
86
- fs.writeFileSync(argFile, [
87
- '-ir-output-name', 'compose_stubs',
88
- '-ir-output-dir', this.workDir,
89
- '-libraries', this.stdlibKlib!,
90
- '-module-kind', 'es',
91
- '-target', 'es2015',
92
- '-Xir-produce-klib-file',
93
- src,
94
- ].join('\n'), 'utf8');
89
+ fs.writeFileSync(
90
+ argFile,
91
+ [
92
+ '-ir-output-name',
93
+ 'compose_stubs',
94
+ '-ir-output-dir',
95
+ this.workDir,
96
+ '-libraries',
97
+ this.stdlibKlib!,
98
+ '-module-kind',
99
+ 'es',
100
+ '-target',
101
+ 'es2015',
102
+ '-Xir-produce-klib-file',
103
+ src,
104
+ ].join('\n'),
105
+ 'utf8'
106
+ );
95
107
 
96
108
  log('[JsCompiler] Building Compose shims klib (one-time, ~30s)...');
97
109
  const r = spawnSync(this.kotlincJsPath!, [`@${argFile}`], {
98
- shell: true, encoding: 'utf8', timeout: 120000,
110
+ shell: true,
111
+ encoding: 'utf8',
112
+ timeout: 120000,
99
113
  });
100
114
 
101
115
  if ((r.status === 0 || !r.stderr?.includes('error:')) && fs.existsSync(outKlib)) {
@@ -181,7 +195,7 @@ export class JsCompilerService {
181
195
  */
182
196
  private removeElseBlockIf(text: string, predicate: (body: string) => boolean): string {
183
197
  const re = /\}\s*else\s*\{([^{}]|\{[^{}]*\})*\}/gs;
184
- return text.replace(re, (match) => predicate(match) ? '}' : match);
198
+ return text.replace(re, (match) => (predicate(match) ? '}' : match));
185
199
  }
186
200
 
187
201
  /**
@@ -195,7 +209,7 @@ export class JsCompilerService {
195
209
  private preprocessFile(ktFilePath: string, runDir: string): string {
196
210
  let text = fs.readFileSync(ktFilePath, 'utf8');
197
211
 
198
- // ── Pass 1: Whole-file transformations ────────────────────────────────────
212
+ // Pass 1: Whole-file transformations
199
213
  // Imports / package
200
214
  text = text.replace(/^import\s+.*/gm, '');
201
215
  text = text.replace(/^package\s+.*/gm, '');
@@ -222,24 +236,36 @@ export class JsCompilerService {
222
236
  text = text.replace(/\bviewModel\.\w+\b/g, 'Unit');
223
237
  // Delegate by remember
224
238
  text = text.replace(/\bval\s+(\w+)\s+by\s+remember\s*\{[^}]+\}/g, 'val $1 = ""');
225
- text = text.replace(/\bvar\s+(\w+)\s+by\s+remember\s*\{\s*mutableStateOf\((false|true)\)\s*\}/g, 'var $1: Boolean = $2');
239
+ text = text.replace(
240
+ /\bvar\s+(\w+)\s+by\s+remember\s*\{\s*mutableStateOf\((false|true)\)\s*\}/g,
241
+ 'var $1: Boolean = $2'
242
+ );
226
243
  text = text.replace(/\bvar\s+(\w+)\s+by\s+remember\s*\{[^}]+\}/g, 'var $1 = ""');
227
244
  // Data class fields
228
245
  text = text.replace(/\bnote\.content\b/g, '"Note content"');
229
246
  text = text.replace(/\bnote\.\w+\b/g, '"preview"');
230
247
  text = text.replace(/\b(\w+)\s*:\s*Note\b/g, '$1: String = "Note"');
231
248
  // Preview lists
232
- text = text.replace(/\bval\s+suggestedTags\b[^\n]*/g, 'val suggestedTags: List<String> = listOf()');
249
+ text = text.replace(
250
+ /\bval\s+suggestedTags\b[^\n]*/g,
251
+ 'val suggestedTags: List<String> = listOf()'
252
+ );
233
253
  text = text.replace(/suggestedTags\.isEmpty\(\)/g, 'true');
234
254
  text = text.replace(/suggestedTags\.isNotEmpty\(\)/g, 'false');
235
- text = text.replace(/\bval\s+searchResults\b[^\n]*/g,
236
- 'val searchResults: List<String> = listOf("Note 1", "Note 2", "Note 3")');
255
+ text = text.replace(
256
+ /\bval\s+searchResults\b[^\n]*/g,
257
+ 'val searchResults: List<String> = listOf("Note 1", "Note 2", "Note 3")'
258
+ );
237
259
  // Named M3 colors
238
260
  const colorMap: Record<string, string> = {
239
- onSurfaceVariant: '"#49454F"', onBackground: '"#1C1B1F"',
240
- outline: '"#79747E"', surfaceVariant: '"#E7E0EC"',
241
- onPrimaryContainer: '"#21005D"', inverseOnSurface: '"#F4EFF4"',
242
- inverseSurface: '"#313033"', tertiaryContainer: '"#FFD8E4"',
261
+ onSurfaceVariant: '"#49454F"',
262
+ onBackground: '"#1C1B1F"',
263
+ outline: '"#79747E"',
264
+ surfaceVariant: '"#E7E0EC"',
265
+ onPrimaryContainer: '"#21005D"',
266
+ inverseOnSurface: '"#F4EFF4"',
267
+ inverseSurface: '"#313033"',
268
+ tertiaryContainer: '"#FFD8E4"',
243
269
  };
244
270
  for (const [k, v] of Object.entries(colorMap)) {
245
271
  text = text.replace(new RegExp(`MaterialTheme\\.colorScheme\\.${k}\\b`, 'g'), v);
@@ -254,15 +280,24 @@ export class JsCompilerService {
254
280
  text = text.replace(/PaddingValues\((\d+)\)/g, '$1');
255
281
  text = text.replace(/PaddingValues\([^)]*\)/g, '0');
256
282
  // Named padding β†’ single value
257
- text = text.replace(/\.padding\(\s*(?:start|left|horizontal)\s*=\s*(\d+)[^)]*\)/g, '.padding($1)');
283
+ text = text.replace(
284
+ /\.padding\(\s*(?:start|left|horizontal)\s*=\s*(\d+)[^)]*\)/g,
285
+ '.padding($1)'
286
+ );
258
287
  text = text.replace(/\.padding\(\s*(?:top|vertical)\s*=\s*(\d+)[^)]*\)/g, '.padding($1)');
259
288
  text = text.replace(/\.padding\(padding\)/g, '.padding(0)');
260
289
  // Shape removal
261
- text = text.replace(/,?\s*shape\s*=\s*(?:MaterialTheme\.shapes\.\w+|RoundedCornerShape\([^)]*\)|CircleShape)/g, '');
290
+ text = text.replace(
291
+ /,?\s*shape\s*=\s*(?:MaterialTheme\.shapes\.\w+|RoundedCornerShape\([^)]*\)|CircleShape)/g,
292
+ ''
293
+ );
262
294
  // Modifier
263
295
  text = text.replace(/\bModifier\b(?!\(\)|[\w])/g, 'Modifier()');
264
296
  text = text.replace(/\bModifier\(\)\s*\n\s*\./g, 'Modifier().');
265
- text = text.replace(/\)\s*\n\s*\.(fill|padding|height|width|size|weight|background|clip|alpha|wrap)/g, ').$1');
297
+ text = text.replace(
298
+ /\)\s*\n\s*\.(fill|padding|height|width|size|weight|background|clip|alpha|wrap)/g,
299
+ ').$1'
300
+ );
266
301
  // horizontalArrangement not in staggered grid stubs
267
302
  text = this.removeHorizontalArrangementOutsideRow(text);
268
303
  // Scroll
@@ -286,7 +321,7 @@ export class JsCompilerService {
286
321
  text = text.replace(/StaggeredGridCells\.Fixed\((\d+)\)\),/g, 'StaggeredGridCells.Fixed($1),');
287
322
  text = text.replace(/modifier\s*=\s*Modifier\(\)\),/g, 'modifier = Modifier(),');
288
323
 
289
- // ── Pass 2: Brace-balanced block removal ──────────────────────────────────
324
+ // Pass 2: Brace-balanced block removal
290
325
  // LaunchedEffect / SideEffect / DisposableEffect
291
326
  for (const kw of ['LaunchedEffect', 'DisposableEffect', 'SideEffect']) {
292
327
  text = this.removeBalancedBlock(text, new RegExp(`\\b${kw}\\b[^{]*\\{`, 'g'));
@@ -300,18 +335,18 @@ export class JsCompilerService {
300
335
  // if (X.isNotEmpty()) { ... } β€” generic tag guards
301
336
  text = this.removeBalancedBlock(text, /if\s*\(\s*\w+\.isNotEmpty\(\)\s*\)\s*\{/g);
302
337
  // Remove else blocks containing broken/removed suggestedTags code
303
- text = this.removeElseBlockIf(text, (body) =>
304
- body.includes('suggestedTags') || body.includes('Modifier()),')
338
+ text = this.removeElseBlockIf(
339
+ text,
340
+ (body) => body.includes('suggestedTags') || body.includes('Modifier()),')
305
341
  );
306
342
 
307
- // ── Pass 3: Final cleanup ─────────────────────────────────────────────────
343
+ // Pass 3: Final cleanup
308
344
  text = text.replace(/\n{3,}/g, '\n\n');
309
345
  text = text.replace(/,\s*\)/g, ')');
310
346
  text = text.replace(/\(\s*,/g, '(');
311
347
 
312
348
  // Detect the screen function name to wrap the entry point
313
- const screenMatch = text.match(/^fun\s+(\w+Screen)/m) ||
314
- text.match(/^fun\s+(\w+)/m);
349
+ const screenMatch = text.match(/^fun\s+(\w+Screen)/m) || text.match(/^fun\s+(\w+)/m);
315
350
  const screenName = screenMatch ? screenMatch[1] : null;
316
351
  const renderWrapper = screenName
317
352
  ? `\n\n@OptIn(kotlin.js.ExperimentalJsExport::class)\n@JsExport\nfun __jetstart_render__(): dynamic = renderScreen { ${screenName}() }\n`
@@ -323,7 +358,6 @@ export class JsCompilerService {
323
358
  return preprocessedPath;
324
359
  }
325
360
 
326
-
327
361
  async compile(ktFilePath: string): Promise<JsCompileResult> {
328
362
  if (!this.isAvailable()) return { success: false, error: 'kotlinc-js unavailable' };
329
363
 
@@ -336,7 +370,7 @@ export class JsCompilerService {
336
370
  fs.mkdirSync(runDir, { recursive: true });
337
371
 
338
372
  try {
339
- // ── Step 1: .kt β†’ screen.klib ─────────────────────────────────────────
373
+ // Step 1: .kt β†’ screen.klib
340
374
  const screenKlib = path.join(runDir, 'screen.klib');
341
375
  const args1 = path.join(runDir, 'step1.txt');
342
376
  const libs1 = [this.stdlibKlib!, stubsKlib].join(process.platform === 'win32' ? ';' : ':');
@@ -344,49 +378,77 @@ export class JsCompilerService {
344
378
  // Preprocess: strip Android imports, annotations, and types not available in JS
345
379
  const preprocessedKtPath = this.preprocessFile(ktFilePath, runDir);
346
380
 
347
- fs.writeFileSync(args1, [
348
- '-ir-output-name', 'screen',
349
- '-ir-output-dir', runDir,
350
- '-libraries', libs1,
351
- '-module-kind', 'es',
352
- '-target', 'es2015',
353
- '-Xir-produce-klib-file',
354
- preprocessedKtPath,
355
- ].join('\n'), 'utf8');
381
+ fs.writeFileSync(
382
+ args1,
383
+ [
384
+ '-ir-output-name',
385
+ 'screen',
386
+ '-ir-output-dir',
387
+ runDir,
388
+ '-libraries',
389
+ libs1,
390
+ '-module-kind',
391
+ 'es',
392
+ '-target',
393
+ 'es2015',
394
+ '-Xir-produce-klib-file',
395
+ preprocessedKtPath,
396
+ ].join('\n'),
397
+ 'utf8'
398
+ );
356
399
 
357
400
  const r1 = spawnSync(this.kotlincJsPath!, [`@${args1}`], {
358
- shell: true, encoding: 'utf8', timeout: 120000,
401
+ shell: true,
402
+ encoding: 'utf8',
403
+ timeout: 120000,
359
404
  });
360
405
 
361
406
  if (!fs.existsSync(screenKlib)) {
362
- const errLines = (r1.stderr || r1.stdout || '').split('\n')
363
- .filter((l: string) => l.includes('error:')).slice(0, 8).join('\n');
407
+ const errLines = (r1.stderr || r1.stdout || '')
408
+ .split('\n')
409
+ .filter((l: string) => l.includes('error:'))
410
+ .slice(0, 8)
411
+ .join('\n');
364
412
  return { success: false, error: `Step 1 failed:\n${errLines}` };
365
413
  }
366
414
 
367
- // ── Step 2: screen.klib β†’ screen.mjs ──────────────────────────────────
415
+ // Step 2: screen.klib β†’ screen.mjs
368
416
  const outDir = path.join(runDir, 'out');
369
417
  fs.mkdirSync(outDir, { recursive: true });
370
418
  const args2 = path.join(runDir, 'step2.txt');
371
419
 
372
- fs.writeFileSync(args2, [
373
- '-ir-output-name', 'screen',
374
- '-ir-output-dir', outDir,
375
- '-libraries', libs1,
376
- '-module-kind', 'es',
377
- '-target', 'es2015',
378
- '-Xir-produce-js',
379
- `-Xinclude=${screenKlib}`,
380
- ].join('\n'), 'utf8');
420
+ fs.writeFileSync(
421
+ args2,
422
+ [
423
+ '-ir-output-name',
424
+ 'screen',
425
+ '-ir-output-dir',
426
+ outDir,
427
+ '-libraries',
428
+ libs1,
429
+ '-module-kind',
430
+ 'es',
431
+ '-target',
432
+ 'es2015',
433
+ '-Xir-produce-js',
434
+ `-Xinclude=${screenKlib}`,
435
+ ].join('\n'),
436
+ 'utf8'
437
+ );
381
438
 
382
439
  const r2 = spawnSync(this.kotlincJsPath!, [`@${args2}`], {
383
- shell: true, encoding: 'utf8', timeout: 120000,
440
+ shell: true,
441
+ encoding: 'utf8',
442
+ timeout: 120000,
384
443
  });
385
444
 
386
445
  const mjsPath = path.join(outDir, 'screen.mjs');
387
446
  if (!fs.existsSync(mjsPath)) {
388
- const errLines = (r2.stderr || r2.stdout || '').split('\n')
389
- .filter((l: string) => !l.startsWith('warning:') && l.trim()).slice(0, 8).join('\n');
447
+ const errLines = (r2.stderr || r2.stdout || '')
448
+ .split('\n')
449
+ .filter((l: string) => !l.startsWith('warning:') && l.trim())
450
+ .slice(0, 8)
451
+ .join('\n');
390
452
  return { success: false, error: `Step 2 failed:\n${errLines}` };
391
453
  }
392
454
 
@@ -398,14 +460,25 @@ export class JsCompilerService {
398
460
  const mjsBytes = fs.readFileSync(mjsPath);
399
461
  const jsBase64 = mjsBytes.toString('base64');
400
462
  const elapsed = Date.now() - t0;
401
- log(`[JsCompiler] βœ… ${path.basename(ktFilePath)} β†’ JS in ${elapsed}ms (${mjsBytes.length} bytes)`);
402
-
403
- return { success: true, jsBase64, byteSize: mjsBytes.length, compileTimeMs: elapsed, screenFunctionName };
463
+ log(
464
+ `[JsCompiler] βœ… ${path.basename(ktFilePath)} β†’ JS in ${elapsed}ms (${mjsBytes.length} bytes)`
465
+ );
466
+
467
+ return {
468
+ success: true,
469
+ jsBase64,
470
+ byteSize: mjsBytes.length,
471
+ compileTimeMs: elapsed,
472
+ screenFunctionName,
473
+ };
404
474
  } finally {
405
- try { fs.rmSync(runDir, { recursive: true, force: true }); } catch {}
475
+ try {
476
+ fs.rmSync(runDir, { recursive: true, force: true });
477
+ } catch {}
406
478
  }
407
479
  }
408
480
  }
409
481
 
410
- // ── Compose shim stubs (validated against kotlinc-js 2.3.0) ──────────────────
411
- const COMPOSE_STUBS = "package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js(\"({type:'root',children:[]})\")\n val stack: dynamic = js(\"([])\")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β€” prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js(\"({type:'slot',children:[]})\")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js(\"({})\")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = \"top\"; val Bottom = \"bottom\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val SpaceBetween = \"space-between\"; val SpaceEvenly = \"space-evenly\"; val SpaceAround = \"space-around\"\n fun spacedBy(dp: Int) = \"spacedBy(${\"$\"}dp)\"\n fun spacedBy(dp: Float) = \"spacedBy(${\"$\"}{dp.toInt()})\"\n}\nobject Alignment {\n val Top = \"top\"; val Bottom = \"bottom\"; val CenterVertically = \"centerVertically\"\n val CenterHorizontally = \"centerHorizontally\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val TopStart = \"topStart\"; val TopEnd = \"topEnd\"\n val BottomStart = \"bottomStart\"; val BottomEnd = \"bottomEnd\"\n val TopCenter = \"topCenter\"; val BottomCenter = \"bottomCenter\"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = \"#6750A4\"; val onPrimary = \"#FFFFFF\"\n val secondary = \"#625B71\"; val surface = \"#FFFBFE\"\n val background = \"#FFFBFE\"; val error = \"#B3261E\"\n val primaryContainer = \"#EADDFF\"; val secondaryContainer = \"#E8DEF8\"\n val surfaceVariant = \"#E7E0EC\"; val outline = \"#79747E\"\n val onBackground = \"#1C1B1F\"; val onSurface = \"#1C1B1F\"\n val tertiaryContainer = \"#FFD8E4\"; val inverseSurface = \"#313033\"\n val inverseOnSurface = \"#F4EFF4\"; val onPrimaryContainer = \"#21005D\"\n}\nclass Typography {\n val displayLarge = \"displayLarge\"; val displayMedium = \"displayMedium\"; val displaySmall = \"displaySmall\"\n val headlineLarge = \"headlineLarge\"; val headlineMedium = \"headlineMedium\"; val headlineSmall = \"headlineSmall\"\n val titleLarge = \"titleLarge\"; val titleMedium = \"titleMedium\"; val titleSmall = \"titleSmall\"\n val bodyLarge = \"bodyLarge\"; val bodyMedium = \"bodyMedium\"; val bodySmall = \"bodySmall\"\n val labelLarge = \"labelLarge\"; val labelMedium = \"labelMedium\"; val labelSmall = \"labelSmall\"\n}\nclass Shapes { val small=\"small\"; val medium=\"medium\"; val large=\"large\"; val extraLarge=\"extraLarge\" }\nobject FontWeight { val Bold=\"bold\"; val Normal=\"normal\"; val Light=\"light\"; val Medium=\"medium\"; val SemiBold=\"semibold\" }\nobject TextAlign { val Start=\"start\"; val Center=\"center\"; val End=\"end\"; val Justify=\"justify\" }\nobject ContentScale { val Crop=\"crop\"; val Fit=\"fit\"; val FillBounds=\"fillBounds\"; val FillWidth=\"fillWidth\" }\nobject Color {\n val White = \"#FFFFFF\"; val Black = \"#000000\"; val Transparent = \"transparent\"\n val Red = \"#F44336\"; val Blue = \"#2196F3\"; val Green = \"#4CAF50\"\n val Gray = \"#9E9E9E\"; val LightGray = \"#E0E0E0\"; val DarkGray = \"#424242\"\n val Unspecified = \"unspecified\"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = \"string_${\"$\"}id\"\nfun painterResource(id: Int): Any = js(\"({type:'resource',id:${\"$\"}id})\")\n\nobject Icons {\n object Default {\n val Add: Any = js(\"({icon:'add'})\")\n val Search: Any = js(\"({icon:'search'})\")\n val Close: Any = js(\"({icon:'close'})\")\n val Check: Any = js(\"({icon:'check'})\")\n val Delete: Any = js(\"({icon:'delete'})\")\n val Edit: Any = js(\"({icon:'edit'})\")\n val Home: Any = js(\"({icon:'home'})\")\n val Menu: Any = js(\"({icon:'menu'})\")\n val Settings: Any = js(\"({icon:'settings'})\")\n val ArrowBack: Any = js(\"({icon:'arrow_back'})\")\n val MoreVert: Any = js(\"({icon:'more_vert'})\")\n val Favorite: Any = js(\"({icon:'favorite'})\")\n val Share: Any = js(\"({icon:'share'})\")\n val Info: Any = js(\"({icon:'info'})\")\n val Person: Any = js(\"({icon:'person'})\")\n val Star: Any = js(\"({icon:'star'})\")\n val Notifications: Any = js(\"({icon:'notifications'})\")\n val Email: Any = js(\"({icon:'email'})\")\n val Phone: Any = js(\"({icon:'phone'})\")\n val Lock: Any = js(\"({icon:'lock'})\")\n val Visibility: Any = js(\"({icon:'visibility'})\")\n val VisibilityOff: Any = js(\"({icon:'visibility_off'})\")\n val ShoppingCart: Any = js(\"({icon:'shopping_cart'})\")\n val KeyboardArrowDown: Any = js(\"({icon:'keyboard_arrow_down'})\")\n val KeyboardArrowUp: Any = js(\"({icon:'keyboard_arrow_up'})\")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ── Layout composables ────────────────────────────────────────────────────────\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any=\"\", horizontalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Column',children:[]})\")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any=\"\", verticalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Row',children:[]})\")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any=\"topStart\", content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'Box',children:[]})\")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any=\"\", color: Any=\"\", fontSize: Any=\"\", fontWeight: Any=\"\", textAlign: Any=\"\", maxLines: Int=Int.MAX_VALUE, overflow: Any=\"\") {\n val n: dynamic = js(\"({type:'Text',children:[]})\")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) \"bodyMedium\" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any=\"\", colors: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Button',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'TextButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'FilledTonalButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'FloatingActionButton',children:[]})\")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js(\"({type:'ExtendedFAB',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'IconButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", colors: Any=\"\", elevation: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Card',children:[]})\")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any=\"\", content: (Any)->Unit) {\n val n: dynamic = js(\"({type:'Scaffold',children:[],topBar:[],bottomBar:[],fab:[]})\")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js(\"({})\")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") {\n val n: dynamic = js(\"({type:'TopAppBar',children:[],title:[],actions:[]})\")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'NavigationBar',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'NavigationBarItem',children:[],label:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'OutlinedTextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'TextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'SearchBar',children:[]})\")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", verticalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyColumn',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyRow',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", verticalArrangement: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalStaggeredGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any=\"\") {\n val n: dynamic = js(\"({type:'Icon',children:[]})\")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else \"default\"\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js(\"({type:'Image',children:[]})\")\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'Spacer',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") {\n val n: dynamic = js(\"({type:'Divider',children:[]})\")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Switch',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Checkbox',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any=\"\", strokeWidth: Int=4) {\n val n: dynamic = js(\"({type:'CircularProgressIndicator',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any=\"\") {\n val n: dynamic = js(\"({type:'LinearProgressIndicator',children:[]})\")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'assist',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'filter',children:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'suggestion',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'AlertDialog',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})\")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js(\"({type:'DropdownMenu',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js(\"({type:'DropdownMenuItem',children:[]})\")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js(\"({})\")\nfun rememberScrollState(): Any = js(\"({})\")\nfun SnackbarHostState(): Any = js(\"({})\")\nfun rememberSnackbarHostState(): Any = js(\"({})\")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js(\"({})\")\nobject KeyboardOptions { val Default = js(\"({})\") }\nobject KeyboardActions { val Default = js(\"({})\") }\nobject ImeAction { val Done=\"done\"; val Search=\"search\"; val Go=\"go\"; val Next=\"next\" }\nobject KeyboardType { val Text=\"text\"; val Number=\"number\"; val Email=\"email\"; val Password=\"password\" }\nfun PaddingValues(all: Int=0): Any = js(\"({})\")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js(\"({})\")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js(\"({})\")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ── Entry point called by the browser ────────────────────────────────────────\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}";
482
+ // Compose shim stubs (validated against kotlinc-js 2.3.0)
483
+ const COMPOSE_STUBS =
484
+ 'package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js("({type:\'root\',children:[]})")\n val stack: dynamic = js("([])")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β€” prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js("({type:\'slot\',children:[]})")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js("({})")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = "top"; val Bottom = "bottom"; val Center = "center"\n val Start = "start"; val End = "end"\n val SpaceBetween = "space-between"; val SpaceEvenly = "space-evenly"; val SpaceAround = "space-around"\n fun spacedBy(dp: Int) = "spacedBy(${"$"}dp)"\n fun spacedBy(dp: Float) = "spacedBy(${"$"}{dp.toInt()})"\n}\nobject Alignment {\n val Top = "top"; val Bottom = "bottom"; val CenterVertically = "centerVertically"\n val CenterHorizontally = "centerHorizontally"; val Center = "center"\n val Start = "start"; val End = "end"\n val TopStart = "topStart"; val TopEnd = "topEnd"\n val BottomStart = "bottomStart"; val BottomEnd = "bottomEnd"\n val TopCenter = "topCenter"; val BottomCenter = "bottomCenter"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = "#6750A4"; val onPrimary = "#FFFFFF"\n val secondary = "#625B71"; val surface = "#FFFBFE"\n val background = "#FFFBFE"; val error = "#B3261E"\n val primaryContainer = "#EADDFF"; val secondaryContainer = "#E8DEF8"\n val surfaceVariant = "#E7E0EC"; val outline = "#79747E"\n val onBackground = "#1C1B1F"; val onSurface = "#1C1B1F"\n val tertiaryContainer = "#FFD8E4"; val inverseSurface = "#313033"\n val inverseOnSurface = "#F4EFF4"; val onPrimaryContainer = "#21005D"\n}\nclass Typography {\n val displayLarge = "displayLarge"; val displayMedium = "displayMedium"; val displaySmall = "displaySmall"\n val headlineLarge = "headlineLarge"; val headlineMedium = "headlineMedium"; val headlineSmall = "headlineSmall"\n val titleLarge = "titleLarge"; val titleMedium = "titleMedium"; val titleSmall = "titleSmall"\n val bodyLarge = "bodyLarge"; val bodyMedium = "bodyMedium"; val bodySmall = "bodySmall"\n val labelLarge = "labelLarge"; val labelMedium = "labelMedium"; val labelSmall = "labelSmall"\n}\nclass Shapes { val small="small"; val medium="medium"; val large="large"; val extraLarge="extraLarge" }\nobject FontWeight { val Bold="bold"; val Normal="normal"; val Light="light"; val Medium="medium"; val SemiBold="semibold" }\nobject TextAlign { val Start="start"; val Center="center"; val End="end"; val Justify="justify" }\nobject ContentScale { val Crop="crop"; val Fit="fit"; val FillBounds="fillBounds"; val FillWidth="fillWidth" }\nobject Color {\n val White = "#FFFFFF"; val Black = "#000000"; val Transparent = "transparent"\n val Red = "#F44336"; val Blue = "#2196F3"; val Green = "#4CAF50"\n val Gray = "#9E9E9E"; val LightGray = "#E0E0E0"; val DarkGray = "#424242"\n val Unspecified = "unspecified"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = "string_${"$"}id"\nfun painterResource(id: Int): Any = js("({type:\'resource\',id:${"$"}id})")\n\nobject Icons {\n object Default {\n val Add: Any = js("({icon:\'add\'})")\n val Search: Any = js("({icon:\'search\'})")\n val Close: Any = js("({icon:\'close\'})")\n val Check: Any = js("({icon:\'check\'})")\n val Delete: Any = js("({icon:\'delete\'})")\n val Edit: Any = js("({icon:\'edit\'})")\n val Home: Any = js("({icon:\'home\'})")\n val Menu: Any = js("({icon:\'menu\'})")\n val Settings: Any = js("({icon:\'settings\'})")\n val ArrowBack: Any = js("({icon:\'arrow_back\'})")\n val MoreVert: Any = js("({icon:\'more_vert\'})")\n val Favorite: Any = js("({icon:\'favorite\'})")\n val Share: Any = js("({icon:\'share\'})")\n val Info: Any = js("({icon:\'info\'})")\n val Person: Any = js("({icon:\'person\'})")\n val Star: Any = js("({icon:\'star\'})")\n val Notifications: Any = js("({icon:\'notifications\'})")\n val Email: Any = js("({icon:\'email\'})")\n val Phone: Any = js("({icon:\'phone\'})")\n val Lock: Any = js("({icon:\'lock\'})")\n val Visibility: Any = js("({icon:\'visibility\'})")\n val VisibilityOff: Any = js("({icon:\'visibility_off\'})")\n val ShoppingCart: Any = js("({icon:\'shopping_cart\'})")\n val KeyboardArrowDown: Any = js("({icon:\'keyboard_arrow_down\'})")\n val KeyboardArrowUp: Any = js("({icon:\'keyboard_arrow_up\'})")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ── Layout composables ────────────────────────────────────────────────────────\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any="", horizontalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Column\',children:[]})")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any="", verticalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Row\',children:[]})")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any="topStart", content: ()->Unit={}) {\n val n: dynamic = js("({type:\'Box\',children:[]})")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any="", color: Any="", fontSize: Any="", fontWeight: Any="", textAlign: Any="", maxLines: Int=Int.MAX_VALUE, overflow: Any="") {\n val n: dynamic = js("({type:\'Text\',children:[]})")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) "bodyMedium" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any="", colors: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Button\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'TextButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'FilledTonalButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'FloatingActionButton\',children:[]})")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js("({type:\'ExtendedFAB\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'IconButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", colors: Any="", elevation: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Card\',children:[]})")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any="", content: (Any)->Unit) {\n val n: dynamic = js("({type:\'Scaffold\',children:[],topBar:[],bottomBar:[],fab:[]})")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js("({})")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") {\n val n: dynamic = js("({type:\'TopAppBar\',children:[],title:[],actions:[]})")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'NavigationBar\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'NavigationBarItem\',children:[],label:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'OutlinedTextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'TextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js("({type:\'SearchBar\',children:[]})")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", verticalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyColumn\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyRow\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", verticalArrangement: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalStaggeredGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any="") {\n val n: dynamic = js("({type:\'Icon\',children:[]})")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else "default"\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js("({type:\'Image\',children:[]})")\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'Spacer\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") {\n val n: dynamic = js("({type:\'Divider\',children:[]})")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Switch\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Checkbox\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any="", strokeWidth: Int=4) {\n val n: dynamic = js("({type:\'CircularProgressIndicator\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any="") {\n val n: dynamic = js("({type:\'LinearProgressIndicator\',children:[]})")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'assist\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'filter\',children:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'suggestion\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'AlertDialog\',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js("({type:\'DropdownMenu\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js("({type:\'DropdownMenuItem\',children:[]})")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js("({})")\nfun rememberScrollState(): Any = js("({})")\nfun SnackbarHostState(): Any = js("({})")\nfun rememberSnackbarHostState(): Any = js("({})")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js("({})")\nobject KeyboardOptions { val Default = js("({})") }\nobject KeyboardActions { val Default = js("({})") }\nobject ImeAction { val Done="done"; val Search="search"; val Go="go"; val Next="next" }\nobject KeyboardType { val Text="text"; val Number="number"; val Email="email"; val Password="password" }\nfun PaddingValues(all: Int=0): Any = js("({})")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js("({})")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js("({})")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ── Entry point called by the browser ────────────────────────────────────────\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}';
@@ -2,7 +2,7 @@
2
2
  * Main Server Entry Point
3
3
  * Starts HTTP and WebSocket servers
4
4
  */
5
-
5
+ import 'dotenv/config';
6
6
  import * as path from 'path';
7
7
  import * as fs from 'fs';
8
8
  import * as os from 'os';
@@ -141,8 +141,8 @@ export class JetStartServer extends EventEmitter {
141
141
  port: this.config.wsPort,
142
142
  logsServer: this.logsServer,
143
143
  adbHelper: this.adbHelper, // Enable wireless ADB auto-connect
144
- expectedSessionId: this.currentSession?.id, // Reject old-session devices
145
- expectedToken: this.currentSession?.token, // Validate token too
144
+ expectedSessionId: this.currentSession?.id,
145
+ expectedToken: this.currentSession?.token,
146
146
  projectName: this.config.projectName,
147
147
  onClientConnected: async (sessionId: string) => {
148
148
  log(`Client connected (session: ${sessionId}). Triggering initial build...`);
@@ -250,7 +250,7 @@ export class JetStartServer extends EventEmitter {
250
250
  // Store the APK path
251
251
  this.latestApkPath = result.apkPath || null;
252
252
 
253
- // Send full download URL to client (not just relative path)
253
+ // Send full download URL to client
254
254
  const downloadUrl = `http://${this.config.displayHost}:${this.config.httpPort}/download/app.apk`;
255
255
  this.wsHandler.sendBuildComplete(this.currentSession.id, downloadUrl);
256
256
  log(`APK download URL: ${downloadUrl}`);
@@ -273,7 +273,7 @@ export class JetStartServer extends EventEmitter {
273
273
  const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
274
274
  appPackageName = cfg.packageName || appPackageName;
275
275
  }
276
- } catch (_) { /* empty */ }
276
+ } catch (error) { console.log("couldnt find package name") }
277
277
  await this.adbHelper.launchApp(appPackageName, '.MainActivity');
278
278
  } else {
279
279
  error(`Auto-install failed: ${installResult.error}`);
@@ -331,7 +331,7 @@ export class JetStartServer extends EventEmitter {
331
331
  const result = await this.buildService.build({
332
332
  projectPath: this.config.projectPath,
333
333
  outputPath: path.join(this.config.projectPath, 'build/outputs/apk'),
334
- buildType: 'debug' as any, // BuildType.DEBUG
334
+ buildType: 'debug' as any,
335
335
  minifyEnabled: false,
336
336
  debuggable: true,
337
337
  versionCode: 1,
@@ -10,8 +10,36 @@ import { error as logError } from '../utils/logger';
10
10
 
11
11
  export function setupMiddleware(app: Express): void {
12
12
  // CORS
13
+ const isAllowedOrigin = (origin: string): boolean => {
14
+ // Standard allowed origins
15
+ const allowedOrigins = [
16
+ 'http://localhost:8000',
17
+ 'http://localhost:3000',
18
+ 'http://localhost:8765',
19
+ 'http://localhost:8766',
20
+ 'http://localhost:8767',
21
+ ...(process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : [])
22
+ ];
23
+
24
+ if (allowedOrigins.indexOf(origin) !== -1) return true;
25
+
26
+ // Allow jetstart.site and any subdomains
27
+ if (origin === 'https://jetstart.site' || origin.endsWith('.jetstart.site')) return true;
28
+
29
+ return false;
30
+ };
31
+
13
32
  app.use(cors({
14
- origin: '*',
33
+ origin: (origin, callback) => {
34
+ // Allow requests with no origin (like mobile apps or curl requests)
35
+ if (!origin) return callback(null, true);
36
+
37
+ if (isAllowedOrigin(origin) || !process.env.IS_PROD) {
38
+ callback(null, true);
39
+ } else {
40
+ callback(null, false);
41
+ }
42
+ },
15
43
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
16
44
  allowedHeaders: ['Content-Type', 'Authorization'],
17
45
  }));
@@ -2,9 +2,7 @@
2
2
  * HTTP Routes
3
3
  * REST API endpoints
4
4
  */
5
-
6
5
  import { Express, Request, Response } from 'express';
7
- import path from 'path';
8
6
  import fs from 'fs';
9
7
  import { SessionManager } from '../utils/session';
10
8
  import { generateQRCode } from '../utils/qr';
@@ -20,6 +18,7 @@ export function setupRoutes(
20
18
  ): void {
21
19
  // Root Redirect -> Web Emulator
22
20
  app.get('/', (req: Request, res: Response) => {
21
+ const isProduction = process.env.IS_PROD
23
22
  try {
24
23
  const session = getCurrentSession?.();
25
24
 
@@ -32,7 +31,7 @@ export function setupRoutes(
32
31
  }
33
32
 
34
33
  // Construct redirect URL
35
- const webUrl = 'https://web.jetstart.site';
34
+ const webUrl = isProduction ? 'https://web.jetstart.site': 'http://localhost:8000';
36
35
  const host = req.hostname;
37
36
  const port = req.socket.localPort || 8765;
38
37
  const wsPort = DEFAULT_WS_PORT;
@@ -3,7 +3,6 @@
3
3
  * Manages development sessions
4
4
  */
5
5
 
6
- import { v4 as uuidv4 } from 'uuid';
7
6
  import { ServerSession } from '../types';
8
7
  import { SESSION_TOKEN_EXPIRY } from '@jetstart/shared';
9
8
 
@@ -99,7 +99,7 @@ export class WebSocketHandler {
99
99
  const incomingSession = message.sessionId;
100
100
  const incomingToken = (message as any).token as string | undefined;
101
101
 
102
- // ── Session + token validation ──────────────────────────────────────────
102
+ // Session + token validation
103
103
  // Only validate when the server has an expected session configured.
104
104
  if (this.expectedSessionId) {
105
105
  if (incomingSession !== this.expectedSessionId) {
@@ -107,7 +107,7 @@ export class WebSocketHandler {
107
107
  `Rejected client ${clientId}: wrong session "${incomingSession}" (expected "${this.expectedSessionId}")`
108
108
  );
109
109
  logError('This device was built against a different jetstart dev session. Rescan the QR code.');
110
- // Close the WebSocket immediately β€” do not accept this client.
110
+ // Close the WebSocket immediately β€” does not accept this client.
111
111
  const ws = this.connectionManager.getConnection(clientId);
112
112
  if (ws) {
113
113
  ws.close(4001, 'Session mismatch β€” rescan QR code');
@@ -129,7 +129,7 @@ export class WebSocketHandler {
129
129
  }
130
130
  }
131
131
 
132
- // ── Accepted ─────────────────────────────────────────────────────────────
132
+ // Accepted
133
133
  log(`Client accepted (session: ${incomingSession})`);
134
134
  this.connectionManager.setClientSession(clientId, incomingSession);
135
135
 
@@ -196,7 +196,7 @@ export class WebSocketHandler {
196
196
  * Send compiled Kotlinβ†’JS ES module to web emulator clients.
197
197
  * The browser imports it dynamically and renders the Compose UI as HTML.
198
198
  */
199
- sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number, screenFunctionName: string): void {
199
+ sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number, _screenFunctionName: string): void {
200
200
  const message: CoreJsUpdateMessage = {
201
201
  type: 'core:js-update',
202
202
  timestamp: Date.now(),
@@ -58,14 +58,6 @@ export class ConnectionManager {
58
58
  }
59
59
  }
60
60
 
61
- /**
62
- * Broadcast to all clients (UNSAFE - use broadcastToSession instead)
63
- * @deprecated Use broadcastToSession for session isolation
64
- */
65
- broadcast(message: CoreMessage): void {
66
- console.warn('[ConnectionManager] WARNING: Using unsafe broadcast() - use broadcastToSession() instead');
67
- this.broadcastToAll(message);
68
- }
69
61
 
70
62
  /**
71
63
  * Broadcast to all clients in a specific session (SECURE)