agent-flutter 0.1.25 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-flutter",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Portable Flutter skill/rule pack initializer for multiple AI IDEs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -74,3 +74,30 @@ Finalize this feature:
74
74
  - Screenshot evidence: [No UI OR before/after]
75
75
  - Paired with: [Solo OR name]
76
76
  ```
77
+
78
+ ---
79
+
80
+ ## **5. Refactor Converted UI (Post-Convert)**
81
+
82
+ Use this when code comes from convert script and needs production refactor:
83
+
84
+ ```text
85
+ Refactor converted UI:
86
+ - Source file: [e.g. lib/src/ui/home_demo_figma/home_demo_figma_page.dart]
87
+ - Feature name: [e.g. home_demo]
88
+ - Keep exact Figma visual: [yes/no]
89
+ - Needs API integration now?: [yes/no]
90
+ - Normalize/rename convert assets?: [yes/no]
91
+ - Commit now?: [yes/no]
92
+ - Push now?: [yes/no]
93
+ - Create PR now?: [yes/no]
94
+ - Priority fixes: [naming, localization, App* widgets, token cleanup, asset-rename, overflow]
95
+ ```
96
+
97
+ Expected behavior:
98
+ 1. Apply `ui-refactor-convert.md` + `ui.md`.
99
+ 2. Rename generated classes/files to semantic names.
100
+ 3. Normalize convert-generated asset folder and names, then update `AppAssets` usages.
101
+ 4. Replace hardcoded style/string with tokens + localization.
102
+ 5. Extract components and pass UI correctness gates.
103
+ 6. Ask and execute in order: commit -> push -> create PR.
@@ -21,7 +21,7 @@ This is the required "done" flow before feature handoff.
21
21
  6. If answer is `yes`, push branch to remote.
22
22
  7. Ask user explicitly: `Do you want me to create PR now? (yes/no)`
23
23
  8. If answer is `yes`, create PR with mandatory template sections.
24
- 9. If any answer is `no`, stop at that step and return current status.
24
+ 9. If answer is `no` at any step, stop at that step and return current status.
25
25
 
26
26
  If any step is skipped, feature is not considered complete.
27
27
 
@@ -48,6 +48,6 @@ PR must follow `.github/pull_request_template.md` and include all sections:
48
48
 
49
49
  ## 6. Agent Behavior
50
50
  - After finishing UI/API tasks, agent must ask in order: commit -> push -> create PR.
51
- - Agent must execute only steps explicitly confirmed by user (`yes`).
51
+ - Agent executes only steps explicitly confirmed by user (`yes`).
52
52
  - If user confirms all steps, agent returns PR URL as final output.
53
- - If user declines at any step, agent stops and returns current repository status.
53
+ - If user declines at any step, agent stops and returns repository status.
@@ -0,0 +1,120 @@
1
+ ---
2
+ alwaysApply: false
3
+ ---
4
+ # UI Refactor Rule for Convert Output (JSX/Figma -> Dart)
5
+
6
+ ## 1. Goal
7
+ Turn generated UI code into production-ready Flutter code that fully complies with `ui.md`.
8
+
9
+ This rule applies after running convert scripts (for example files like `home_demo_figma_page.dart` and generated component trees), especially when convert output creates non-standard asset names/folders.
10
+
11
+ ## 1.1 Required References
12
+ Always apply together with:
13
+ - [ui.md](./ui.md)
14
+ - [flutter-ui-widgets](../skills/flutter-ui-widgets/SKILL.md)
15
+ - [flutter-assets-management](../skills/flutter-assets-management/SKILL.md)
16
+ - [getx-localization-standard](../skills/getx-localization-standard/SKILL.md)
17
+ - [dart-best-practices](../skills/dart-best-practices/SKILL.md)
18
+ - [ci-cd-pr.md](./ci-cd-pr.md)
19
+
20
+ If API data is involved, also apply:
21
+ - [integration-api.md](./integration-api.md)
22
+ - [dart-model-reuse](../skills/dart-model-reuse/SKILL.md)
23
+
24
+ ## 2. Trigger Conditions
25
+ Use this rule when at least one condition is true:
26
+ - Source file is generated by convert pipeline.
27
+ - Class/file names are non-semantic (for example: `UhomeUdemoFigma`, `Frame123`).
28
+ - Generated UI has hardcoded strings/colors/styles or deep nested widget trees.
29
+ - Converted UI works visually but fails architecture/readability/maintainability standards.
30
+
31
+ ## 3. Refactor Workflow
32
+
33
+ ### Step 1: Normalize Structure and Naming
34
+ - Keep page architecture:
35
+ - `lib/src/ui/<feature>/{binding,interactor,components}`
36
+ - Rename generated class/file names to semantic names.
37
+ - Keep page entry simple (`<feature>_page.dart` should not contain huge raw trees).
38
+
39
+ ### Step 2: Apply Design Tokens and Shared Widgets
40
+ - Replace raw style values with:
41
+ - `AppColors`
42
+ - `AppStyles`
43
+ - `AppDimensions` and extensions (`n.width`, `n.height`)
44
+ - Replace raw controls with existing `App*` widgets where applicable.
45
+ - Keep exact Figma values when overriding tokens (radius/spacing/typography).
46
+
47
+ ### Step 3: Remove Convert Artifacts
48
+ - Remove redundant wrappers (`ClipRRect + Container` duplicates, empty containers, unnecessary stacks).
49
+ - Reduce deeply nested UI into clear sub-components under `components/`.
50
+ - Add `const` where possible.
51
+ - Avoid `Get.width` direct usage when local layout constraints are more correct (`LayoutBuilder`, `MediaQuery` scoped use).
52
+
53
+ ### Step 4: Localization and Content Cleanup
54
+ - Move all user-facing text to `LocaleKey` + `.tr`.
55
+ - Do not keep demo/static strings inside production widget tree.
56
+ - If temporary mock is needed, source it from `app_demo_data.dart`.
57
+
58
+ ### Step 5: State and Behavior Correctness
59
+ - Ensure required states render correctly:
60
+ - `loading`, `success`, `empty`, `error` (plus `unauthorized` if relevant).
61
+ - Ensure UI behavior is not only visual:
62
+ - tap handlers wired,
63
+ - enabled/disabled state clear,
64
+ - no broken interaction overlays.
65
+
66
+ ### Step 6: Asset and Icon Hygiene
67
+ - Use `AppAssets` only (no raw asset path in widget code).
68
+ - Keep icon/image naming consistent and avoid duplicates.
69
+ - Verify SVG rendering without unexpected tint/color shift.
70
+ - Normalize convert-generated asset folders and names:
71
+ - Detect non-standard convert folders (for example: `assets/figma/**`).
72
+ - Move/rename assets to project-standard locations (`assets/images/**`, `assets/images/icons/**`) when applicable.
73
+ - Rename files to meaningful `snake_case` names based on feature and purpose.
74
+ - Replace convert-style asset constants in `lib/src/utils/app_assets.dart` with UI-standard naming.
75
+ - Update all usages in Dart code to new `AppAssets` constants.
76
+ - Remove orphan/unused old asset files and constants.
77
+
78
+ ### Step 7: Verify and Prepare for PR
79
+ - Run at minimum:
80
+ - `fvm flutter analyze`
81
+ - Validate UI correctness gates from `ui.md`:
82
+ - responsive widths `320/390/430`
83
+ - text scale `1.0/1.3`
84
+ - no overflow warnings in debug logs
85
+
86
+ ### Step 8: Commit/Push/PR Confirmation (Mandatory)
87
+ - After refactor verification, ask user in strict order:
88
+ 1. `Do you want me to commit now? (yes/no)`
89
+ 2. `Do you want me to push now? (yes/no)`
90
+ 3. `Do you want me to create PR now? (yes/no)`
91
+ - Execute only steps explicitly confirmed with `yes`.
92
+ - If user answers `no` at any step, stop there and return current status.
93
+
94
+ ## 4. Mandatory Output of Refactor Task
95
+ After refactor, output must include:
96
+ - List of renamed files/classes.
97
+ - List of extracted components.
98
+ - List of localized keys added/updated.
99
+ - List of token replacements (`raw -> AppColors/AppStyles/AppDimensions`).
100
+ - Asset rename mapping (`old path/name -> new path/name`) and updated `AppAssets` constants.
101
+ - Commit/push/PR confirmation answers and execution results.
102
+ - Remaining known gaps (if any) with exact file paths.
103
+
104
+ ## 5. Anti-Patterns (Do Not)
105
+ - Do not keep generated class names in final code.
106
+ - Do not leave hardcoded colors/text if a project token/key exists.
107
+ - Do not keep convert-only asset naming/folders in final code if they violate project standard.
108
+ - Do not parse API JSON directly in UI widgets.
109
+ - Do not submit refactor that only changes formatting but leaves architectural violations.
110
+
111
+ ## 6. Prompt Template
112
+ Use this prompt to trigger this rule:
113
+
114
+ > Refactor converted UI using `ui-refactor-convert.md` + `ui.md`.
115
+ > Source: `<path-to-generated-page-or-component>`.
116
+ > Keep visual fidelity, but enforce architecture/tokens/localization/state correctness.
117
+ > Extract reusable components and remove convert artifacts.
118
+ > Normalize asset folder/naming and update `AppAssets` references.
119
+ > Then ask: commit now? push now? create PR now?
120
+ > Return changed files and verification checklist results.
@@ -1,48 +1,20 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { createRequire } from 'node:module'
4
- import { fileURLToPath } from 'node:url'
5
4
  import { parse } from '@babel/parser'
6
5
  import traverseModule from '@babel/traverse'
7
6
  const traverse = traverseModule.default
8
7
  import postcss from 'postcss'
9
8
  import safeParser from 'postcss-safe-parser'
10
9
 
11
- const __filename = fileURLToPath(import.meta.url)
12
- const __dirname = path.dirname(__filename)
10
+ const __dirname = path.dirname(new URL(import.meta.url).pathname)
13
11
  const require = createRequire(import.meta.url)
14
12
  let sass = null
15
13
  try {
16
14
  sass = require('sass')
17
15
  } catch {}
18
16
 
19
- function resolveProjectRoot() {
20
- const fromCwd = process.cwd()
21
- if (fs.existsSync(path.resolve(fromCwd, 'pubspec.yaml'))) {
22
- return fromCwd
23
- }
24
- let current = __dirname
25
- while (true) {
26
- if (fs.existsSync(path.resolve(current, 'pubspec.yaml'))) {
27
- return current
28
- }
29
- const parent = path.dirname(current)
30
- if (parent === current) break
31
- current = parent
32
- }
33
- return fromCwd
34
- }
35
-
36
- function readPackageName(projectRoot) {
37
- const pubspecPath = path.resolve(projectRoot, 'pubspec.yaml')
38
- if (!fs.existsSync(pubspecPath)) return 'app'
39
- const content = fs.readFileSync(pubspecPath, 'utf8')
40
- const matched = content.match(/^name:\s*([A-Za-z0-9_]+)/m)
41
- return matched?.[1] || 'app'
42
- }
43
-
44
- const REPO_ROOT = resolveProjectRoot()
45
- const PROJECT_PACKAGE_NAME = readPackageName(REPO_ROOT)
17
+ const REPO_ROOT = path.resolve(__dirname, '../..')
46
18
  const APP_ASSETS_FILE = path.resolve(REPO_ROOT, 'lib/src/utils/app_assets.dart')
47
19
  let ICONS_MAP = {}
48
20
  try {
@@ -745,6 +717,47 @@ function textAlignFromProps(props) {
745
717
  return null
746
718
  }
747
719
 
720
+ function isInlineTextTag(tag) {
721
+ return tag === 'span' || tag === 'p'
722
+ }
723
+
724
+ function mergeTextStyles(baseStyle, ownStyle) {
725
+ if (baseStyle && ownStyle) return `(${baseStyle}).merge(${ownStyle})`
726
+ return ownStyle || baseStyle || null
727
+ }
728
+
729
+ function buildInlineTextSpans(el, cssMap, topClassName, skipRootDecoration, inheritedStyle = null) {
730
+ const className = getClassNameAttr(el)
731
+ const props = className && cssMap[className] ? cssMap[className] : {}
732
+ const style = mergeTextStyles(inheritedStyle, textStyleFromProps(props))
733
+ const spans = []
734
+ for (const c of el.children || []) {
735
+ if (isTextNode(c)) {
736
+ const text = c.value.trim()
737
+ if (!text) continue
738
+ const parts = [`text: ${toDartLiteral(text)}`]
739
+ if (style) parts.push(`style: ${style}`)
740
+ spans.push(`TextSpan(${parts.join(', ')})`)
741
+ continue
742
+ }
743
+ if (c.type !== 'JSXElement') continue
744
+ const childTag = jsxElementName(c).toLowerCase()
745
+ if (childTag === 'br') {
746
+ const parts = [`text: '\\n'`]
747
+ if (style) parts.push(`style: ${style}`)
748
+ spans.push(`TextSpan(${parts.join(', ')})`)
749
+ continue
750
+ }
751
+ if (isInlineTextTag(childTag)) {
752
+ spans.push(...buildInlineTextSpans(c, cssMap, topClassName, skipRootDecoration, style))
753
+ continue
754
+ }
755
+ const child = buildWidgetFromElement(c, cssMap, topClassName, skipRootDecoration)
756
+ spans.push(`WidgetSpan(child: ${child.widget})`)
757
+ }
758
+ return spans
759
+ }
760
+
748
761
  function paddingFromProps(props) {
749
762
  const p = props['padding']
750
763
  const pt = props['padding-top']
@@ -881,15 +894,9 @@ function usesAppOwnedInputBackground(widget) {
881
894
  return normalized.startsWith('AppInput(') || normalized.startsWith('_GeneratedDateTimeField(')
882
895
  }
883
896
 
884
- function usesAppOwnedProgressBarBackground(widget) {
885
- if (!widget) return false
886
- const normalized = String(widget).trim()
887
- return normalized.startsWith('AppProgressBar(')
888
- }
889
-
890
897
  function toDartLiteral(s) {
891
898
  const t = String(s || '')
892
- return `'${t.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
899
+ return `'${t.replace(/\\/g, '\\\\').replace(/\$/g, '\\$').replace(/'/g, "\\'")}'`
893
900
  }
894
901
 
895
902
  function isTextNode(node) {
@@ -1176,6 +1183,13 @@ function simplifySvgWidget(widget, size) {
1176
1183
  return `SizedBox(${width}${height}child: ${svgCall})`
1177
1184
  }
1178
1185
 
1186
+ function applySizeToWidget(widget, size) {
1187
+ if (!size || (size.w == null && size.h == null)) return widget
1188
+ const width = size.w != null ? `width: ${size.w}, ` : ''
1189
+ const height = size.h != null ? `height: ${size.h}, ` : ''
1190
+ return `SizedBox(${width}${height}child: ${widget})`
1191
+ }
1192
+
1179
1193
  function shouldStripIconPositioning(size, position, rotate) {
1180
1194
  return (position != null || rotate != null) && isSmallIconSize(size)
1181
1195
  }
@@ -1297,92 +1311,6 @@ function buildRadioGroupWidgetFromElement(el, cssMap) {
1297
1311
  return `_GeneratedRadioGroup(${widgetArgs.join(', ')})`
1298
1312
  }
1299
1313
 
1300
- function roundNumber(value, digits = 4) {
1301
- const factor = 10 ** digits
1302
- return Math.round(value * factor) / factor
1303
- }
1304
-
1305
- function parsePaddingValues(props) {
1306
- const fromPadding = parseBoxValues(props['padding'])
1307
- if (fromPadding) return fromPadding
1308
- return {
1309
- top: cssPxToDouble(props['padding-top']) || 0,
1310
- right: cssPxToDouble(props['padding-right']) || 0,
1311
- bottom: cssPxToDouble(props['padding-bottom']) || 0,
1312
- left: cssPxToDouble(props['padding-left']) || 0,
1313
- }
1314
- }
1315
-
1316
- function solidColorFromBackground(props) {
1317
- const bg = props['background-color'] || props['background']
1318
- if (!bg || /linear-gradient/i.test(String(bg))) return null
1319
- return cssColorToAppColor(bg)
1320
- }
1321
-
1322
- function gradientOrSolidFromBackground(props) {
1323
- const bgSource = props['background-image'] || props['background']
1324
- if (bgSource && /linear-gradient/i.test(String(bgSource))) {
1325
- const gradient = parseLinearGradient(bgSource)
1326
- if (gradient) return gradient
1327
- }
1328
- const solid = solidColorFromBackground(props)
1329
- if (solid) return `LinearGradient(colors: [${solid}, ${solid}])`
1330
- return null
1331
- }
1332
-
1333
- function buildProgressBarWidgetFromElement(el, cssMap) {
1334
- if (!el || !Array.isArray(el.children)) return null
1335
- const directElements = el.children.filter(c => c.type === 'JSXElement')
1336
- if (directElements.length !== 1) return null
1337
-
1338
- const parentClass = getClassNameAttr(el)
1339
- const child = directElements[0]
1340
- const childClass = getClassNameAttr(child)
1341
- if (!parentClass || !childClass) return null
1342
-
1343
- const parentProps = (cssMap && cssMap[parentClass]) ? cssMap[parentClass] : {}
1344
- const childProps = (cssMap && cssMap[childClass]) ? cssMap[childClass] : {}
1345
-
1346
- const parentSize = normalizeGeneratedSize(sizeFromProps(parentProps))
1347
- const childSize = normalizeGeneratedSize(sizeFromProps(childProps))
1348
- if (childSize.w == null || childSize.h == null) return null
1349
-
1350
- const hasProgressLikeBackground = !!solidColorFromBackground(parentProps)
1351
- const fillGradient = gradientOrSolidFromBackground(childProps)
1352
- if (!hasProgressLikeBackground || !fillGradient) return null
1353
-
1354
- const padding = parsePaddingValues(parentProps)
1355
- let totalWidth = parentSize.w
1356
- if (totalWidth == null) {
1357
- const inferred = childSize.w + (padding.left || 0) + (padding.right || 0)
1358
- if (inferred > childSize.w) totalWidth = inferred
1359
- }
1360
- if (totalWidth == null || totalWidth <= 0) return null
1361
-
1362
- let value = childSize.w / totalWidth
1363
- if (!Number.isFinite(value)) return null
1364
- if (value <= 0 || value > 1.05) return null
1365
- value = Math.max(0, Math.min(1, value))
1366
-
1367
- const height = parentSize.h ?? childSize.h
1368
- if (height == null || height <= 0) return null
1369
-
1370
- const backgroundColor = solidColorFromBackground(parentProps)
1371
- const radius = borderRadiusFromProps(parentProps) || borderRadiusFromProps(childProps)
1372
- const args = [
1373
- `value: ${roundNumber(value)}`,
1374
- `height: ${roundNumber(height, 2)}`,
1375
- `width: ${roundNumber(totalWidth, 2)}`,
1376
- `progressGradient: ${fillGradient}`,
1377
- 'padding: 0',
1378
- 'showDot: false',
1379
- ]
1380
- if (backgroundColor) args.push(`backgroundColor: ${backgroundColor}`)
1381
- if (radius) args.push(`borderRadius: ${radius}`)
1382
-
1383
- return `AppProgressBar(${args.join(', ')})`
1384
- }
1385
-
1386
1314
  function wrapOverflowIfSized(body, size) {
1387
1315
  if (process.env.JSX2FLUTTER_ENABLE_OVERFLOW !== '1') return body
1388
1316
  if (!size || (!size.w && !size.h)) return body
@@ -1413,8 +1341,20 @@ function wrapOverflowIfSized(body, size) {
1413
1341
  return `OverflowBox(${overflowArgs.join(', ')}, child: ${body})`
1414
1342
  }
1415
1343
 
1344
+ function unwrapDirectFlexWidget(widgetExpr) {
1345
+ let current = String(widgetExpr || '').trim()
1346
+ while (/^(?:const\s+)?(?:Expanded|Flexible)\s*\(/.test(current)) {
1347
+ const wrapperMatch = current.match(/^(?:const\s+)?(?:Expanded|Flexible)\s*\(([\s\S]*)\)$/)
1348
+ if (!wrapperMatch) break
1349
+ const childMatch = wrapperMatch[1].match(/\bchild:\s*([\s\S]+)$/)
1350
+ if (!childMatch) break
1351
+ current = childMatch[1].trim()
1352
+ }
1353
+ return current
1354
+ }
1355
+
1416
1356
  function buildGeneratedRippleButton(titleExpr) {
1417
- return `RippleButton(title: ${titleExpr}, backgroundColor: Colors.transparent, minWidth: 0, padding: EdgeInsets.zero, onTap: () {})`
1357
+ return `RippleButton(title: ${titleExpr}, backgroundColor: Colors.transparent,minWidth : 0, padding: EdgeInsets.zero, onTap: () {})`
1418
1358
  }
1419
1359
 
1420
1360
  function buildWidgetFromElement(el, cssMap, topClassName, skipRootDecoration) {
@@ -1469,12 +1409,6 @@ function buildWidgetFromElement(el, cssMap, topClassName, skipRootDecoration) {
1469
1409
  semanticWidget = radioGroupWidget
1470
1410
  }
1471
1411
  }
1472
- if (!semanticWidget) {
1473
- const progressBarWidget = buildProgressBarWidgetFromElement(el, cssMap)
1474
- if (progressBarWidget) {
1475
- semanticWidget = progressBarWidget
1476
- }
1477
- }
1478
1412
  if (!semanticWidget && looksLikeButtonClass(className)) {
1479
1413
  const buttonLabel = uniqueTexts(extractTextsFromElement(el, [])).find(Boolean)
1480
1414
  if (buttonLabel) {
@@ -1525,7 +1459,8 @@ function buildWidgetFromElement(el, cssMap, topClassName, skipRootDecoration) {
1525
1459
  const src = getAttr(el, 'src') || ''
1526
1460
  if (/^https?:\/\//.test(src)) {
1527
1461
  const widget = `Image.network(${toDartLiteral(src)}, fit: BoxFit.cover)`
1528
- return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${widget})` : widget, isAbsolute: !!position, position, zIndex, flexGrow, size }
1462
+ const sized = applySizeToWidget(widget, size)
1463
+ return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${sized})` : sized, isAbsolute: !!position, position, zIndex, flexGrow, size }
1529
1464
  }
1530
1465
  const local = resolveLocalAsset(src, className)
1531
1466
  if (local) {
@@ -1538,7 +1473,8 @@ function buildWidgetFromElement(el, cssMap, topClassName, skipRootDecoration) {
1538
1473
  const finalWidget = (rotate != null && !stripPlacement) ? `Transform.rotate(angle: ${rotate}, child: ${simplified})` : simplified
1539
1474
  return { widget: finalWidget, isAbsolute: stripPlacement ? false : !!position, position: stripPlacement ? null : position, zIndex, flexGrow, size }
1540
1475
  }
1541
- return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${widget})` : widget, isAbsolute: !!position, position, zIndex, flexGrow, size }
1476
+ const sized = applySizeToWidget(widget, size)
1477
+ return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${sized})` : sized, isAbsolute: !!position, position, zIndex, flexGrow, size }
1542
1478
  }
1543
1479
  const asset = assetForClassName(className)
1544
1480
  if (asset) {
@@ -1561,12 +1497,10 @@ function buildWidgetFromElement(el, cssMap, topClassName, skipRootDecoration) {
1561
1497
  return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${widget})` : widget, isAbsolute: !!position, position, zIndex, flexGrow, size }
1562
1498
  }
1563
1499
  if (tag === 'span' || tag === 'p') {
1564
- if (children.length === 1) {
1565
- const widget = children[0].widget
1566
- return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${widget})` : widget, isAbsolute: !!position, position, zIndex, flexGrow, size }
1567
- }
1568
- const widget = children.length
1569
- ? `Row(children: [${children.map(c => c.widget).join(', ')}])`
1500
+ const align = textAlignFromProps(props)
1501
+ const spans = buildInlineTextSpans(el, cssMap, topClassName, skipRootDecoration)
1502
+ const widget = spans.length
1503
+ ? `RichText(text: TextSpan(children: [${spans.join(', ')}])${align ? `, textAlign: ${align}` : ''})`
1570
1504
  : 'SizedBox.shrink()'
1571
1505
  return { widget: rotate != null ? `Transform.rotate(angle: ${rotate}, child: ${widget})` : widget, isAbsolute: !!position, position, zIndex, flexGrow, size }
1572
1506
  }
@@ -1575,27 +1509,24 @@ function buildWidgetFromElement(el, cssMap, topClassName, skipRootDecoration) {
1575
1509
  const flowKids = semanticWidget ? [] : children.filter(c => !c.isAbsolute)
1576
1510
  const absKids = semanticWidget ? [] : children.filter(c => c.isAbsolute).sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0))
1577
1511
  const inputOwnedWidget = usesAppOwnedInputBackground(semanticWidget)
1578
- const progressBarOwnedWidget = usesAppOwnedProgressBarBackground(semanticWidget)
1579
1512
  const wrappedInputOwnedWidget = !semanticWidget && flowKids.length === 1 && usesAppOwnedInputBackground(flowKids[0].widget)
1580
- const wrappedProgressBarOwnedWidget = !semanticWidget && flowKids.length === 1 && usesAppOwnedProgressBarBackground(flowKids[0].widget)
1581
1513
  const appOwnedInputWidget = inputOwnedWidget || wrappedInputOwnedWidget
1582
- const appOwnedProgressBarWidget = progressBarOwnedWidget || wrappedProgressBarOwnedWidget
1583
- const appOwnedStyledWidget = appOwnedInputWidget || appOwnedProgressBarWidget
1584
- const omitBackgroundColor = appOwnedStyledWidget
1514
+ const omitBackgroundColor = appOwnedInputWidget
1585
1515
  const decoration = (skipRootDecoration && className === topClassName)
1586
1516
  ? null
1587
1517
  : boxDecorationFromProps(props, { omitBackgroundColor })
1588
- const effectivePadding = appOwnedStyledWidget ? null : padding
1589
- const effectiveDecoration = appOwnedStyledWidget ? null : decoration
1518
+ const effectivePadding = appOwnedInputWidget ? null : padding
1519
+ const effectiveDecoration = appOwnedInputWidget ? null : decoration
1590
1520
  const layoutSize = (appOwnedInputWidget && size) ? { ...size, h: null } : size
1591
1521
  const flex = flexConfigFromProps(props)
1592
1522
  let flowBody = semanticWidget
1593
1523
  if (!flowBody && flowKids.length) {
1594
1524
  if (flex) {
1525
+ const isScrollableRoot = className === topClassName && process.env.JSX2FLUTTER_MODE !== 'classic'
1595
1526
  const alignSelf = String(props['align-self'] || '').toLowerCase()
1596
- const hasMainAxisSize = flex.isRow
1527
+ const hasMainAxisSize = !isScrollableRoot && (flex.isRow
1597
1528
  ? (layoutSize.w != null || alignSelf === 'stretch')
1598
- : (layoutSize.h != null)
1529
+ : (layoutSize.h != null))
1599
1530
  const gapLiteral = flex.gap ? (flex.isRow ? `${flex.gap}.width` : `${flex.gap}.height`) : null
1600
1531
  const baseKids = flowKids.map(c => {
1601
1532
  if (flex && c.flexGrow > 0 && hasMainAxisSize) {
@@ -1727,6 +1658,7 @@ function generateDart(ast, cssMap, outClassName, outPath) {
1727
1658
  const rootDeco = boxDecorationFromProps(rootProps)
1728
1659
  const mode = process.env.JSX2FLUTTER_MODE
1729
1660
  let inner = rootWidget?.widget || 'const SizedBox.shrink()'
1661
+ inner = unwrapDirectFlexWidget(inner)
1730
1662
  let finalRoot
1731
1663
  if (mode === 'classic') {
1732
1664
  finalRoot = inner
@@ -1752,35 +1684,31 @@ function generateDart(ast, cssMap, outClassName, outPath) {
1752
1684
  const usesCheckbox = finalRoot.includes('_GeneratedCheckbox(') || finalRoot.includes('AppCheckbox(')
1753
1685
  const usesRippleButton = finalRoot.includes('RippleButton(')
1754
1686
  const usesTextGradient = finalRoot.includes('AppTextGradient(')
1755
- const usesProgressBar = finalRoot.includes('AppProgressBar(')
1756
1687
  const usesGetWidth = finalRoot.includes('Get.width')
1757
1688
  const imports = [
1758
1689
  "import 'package:flutter/material.dart';",
1759
- `import 'package:${PROJECT_PACKAGE_NAME}/src/utils/app_colors.dart';`,
1760
- `import 'package:${PROJECT_PACKAGE_NAME}/src/extensions/int_extensions.dart';`,
1690
+ "import 'package:link_home/src/utils/app_colors.dart';",
1691
+ "import 'package:link_home/src/extensions/int_extensions.dart';",
1761
1692
  "import 'package:flutter_svg/flutter_svg.dart';",
1762
- `import 'package:${PROJECT_PACKAGE_NAME}/src/utils/app_assets.dart';`,
1693
+ "import 'package:link_home/src/utils/app_assets.dart';",
1763
1694
  ]
1764
1695
  if (usesInput) {
1765
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/app_input.dart';`)
1696
+ imports.push("import 'package:link_home/src/ui/widgets/app_input.dart';")
1766
1697
  }
1767
1698
  if (usesDateTimeField) {
1768
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/app_input_full_time.dart';`)
1699
+ imports.push("import 'package:link_home/src/ui/widgets/app_input_full_time.dart';")
1769
1700
  }
1770
1701
  if (usesRadio) {
1771
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/app_radio_button.dart';`)
1702
+ imports.push("import 'package:link_home/src/ui/widgets/app_radio_button.dart';")
1772
1703
  }
1773
1704
  if (usesCheckbox) {
1774
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/base/checkbox/app_checkbox.dart';`)
1705
+ imports.push("import 'package:link_home/src/ui/widgets/base/checkbox/app_checkbox.dart';")
1775
1706
  }
1776
1707
  if (usesRippleButton) {
1777
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/base/ripple_button.dart';`)
1708
+ imports.push("import 'package:link_home/src/ui/widgets/base/ripple_button.dart';")
1778
1709
  }
1779
1710
  if (usesTextGradient) {
1780
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/app_text_gradient.dart';`)
1781
- }
1782
- if (usesProgressBar) {
1783
- imports.push(`import 'package:${PROJECT_PACKAGE_NAME}/src/ui/widgets/app_progressbar.dart';`)
1711
+ imports.push("import 'package:link_home/src/ui/widgets/app_text_gradient.dart';")
1784
1712
  }
1785
1713
  if (usesGetWidth) {
1786
1714
  imports.push("import 'package:get/get.dart';")