agent-flutter 0.1.25 → 0.1.27

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.27",
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,138 @@
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
+ - Create/Update feature localization JSON files:
56
+ - `lib/src/locale/json/en/<feature>.json`
57
+ - `lib/src/locale/json/ja/<feature>.json`
58
+ - Create/Update feature key module:
59
+ - `lib/src/locale/keys/<feature>_locale_key.dart`
60
+ - Ensure `lang_en.dart` / `lang_ja.dart` only aggregate feature maps (do not inject random inline feature strings).
61
+ - Ensure EN/JA JSON key parity for the refactored feature.
62
+ - Do not keep demo/static strings inside production widget tree.
63
+ - If temporary mock is needed, source it from `app_demo_data.dart`.
64
+
65
+ ### Step 5: State and Behavior Correctness
66
+ - Ensure required states render correctly:
67
+ - `loading`, `success`, `empty`, `error` (plus `unauthorized` if relevant).
68
+ - Ensure UI behavior is not only visual:
69
+ - tap handlers wired,
70
+ - enabled/disabled state clear,
71
+ - no broken interaction overlays.
72
+
73
+ ### Step 6: Asset and Icon Hygiene
74
+ - Use `AppAssets` only (no raw asset path in widget code).
75
+ - Keep icon/image naming consistent and avoid duplicates.
76
+ - Verify SVG rendering without unexpected tint/color shift.
77
+ - Normalize convert-generated asset folders and names:
78
+ - Detect non-standard convert folders (for example: `assets/figma/**`).
79
+ - Move/rename assets to feature-scoped locations:
80
+ - `assets/images/<feature>/**`
81
+ - `assets/images/icons/<feature>/**`
82
+ - Rename files to meaningful `snake_case` names based on feature and purpose.
83
+ - Replace convert-style asset constants in `lib/src/utils/app_assets.dart` with feature-scoped naming.
84
+ - Update all usages in Dart code to new `AppAssets` constants.
85
+ - Prevent cross-feature collisions (same filename/constant for different meanings).
86
+ - Keep shared assets explicitly under shared scope only when truly reused.
87
+ - Remove orphan/unused old asset files and constants.
88
+
89
+ ### Step 7: Verify and Prepare for PR
90
+ - Run at minimum:
91
+ - `fvm flutter analyze`
92
+ - Validate UI correctness gates from `ui.md`:
93
+ - responsive widths `320/390/430`
94
+ - text scale `1.0/1.3`
95
+ - no overflow warnings in debug logs
96
+
97
+ ### Step 8: Commit/Push/PR Confirmation (Mandatory)
98
+ - After refactor verification, ask user in strict order:
99
+ 1. `Do you want me to commit now? (yes/no)`
100
+ 2. `Do you want me to push now? (yes/no)`
101
+ 3. `Do you want me to create PR now? (yes/no)`
102
+ - Execute only steps explicitly confirmed with `yes`.
103
+ - If user answers `no` at any step, stop there and return current status.
104
+
105
+ ## 4. Mandatory Output of Refactor Task
106
+ After refactor, output must include:
107
+ - List of renamed files/classes.
108
+ - List of extracted components.
109
+ - List of localized keys added/updated.
110
+ - Localization JSON files created/updated per feature (`en/ja`) and parity status.
111
+ - List of token replacements (`raw -> AppColors/AppStyles/AppDimensions`).
112
+ - Asset rename mapping (`old path/name -> new path/name`) and updated feature-scoped `AppAssets` constants.
113
+ - Feature asset placement report (`feature -> directories/constants`) to prove no cross-feature mixing.
114
+ - Commit/push/PR confirmation answers and execution results.
115
+ - Remaining known gaps (if any) with exact file paths.
116
+
117
+ ## 5. Anti-Patterns (Do Not)
118
+ - Do not keep generated class names in final code.
119
+ - Do not leave hardcoded colors/text if a project token/key exists.
120
+ - Do not keep feature localization inline in `lang_en.dart`/`lang_ja.dart` when feature JSON exists.
121
+ - Do not keep unmatched keys between `json/en/<feature>.json` and `json/ja/<feature>.json`.
122
+ - Do not keep convert-only asset naming/folders in final code if they violate project standard.
123
+ - Do not place feature-specific assets in generic folders without feature namespace.
124
+ - Do not reuse ambiguous asset constants across different features.
125
+ - Do not parse API JSON directly in UI widgets.
126
+ - Do not submit refactor that only changes formatting but leaves architectural violations.
127
+
128
+ ## 6. Prompt Template
129
+ Use this prompt to trigger this rule:
130
+
131
+ > Refactor converted UI using `ui-refactor-convert.md` + `ui.md`.
132
+ > Source: `<path-to-generated-page-or-component>`.
133
+ > Keep visual fidelity, but enforce architecture/tokens/localization/state correctness.
134
+ > Extract reusable components and remove convert artifacts.
135
+ > Normalize asset folder/naming by feature and update `AppAssets` with feature-scoped constants.
136
+ > Split localization by feature JSON (en/ja) and update LocaleKey feature module.
137
+ > Then ask: commit now? push now? create PR now?
138
+ > Return changed files and verification checklist results.
@@ -67,18 +67,25 @@ alwaysApply: false
67
67
 
68
68
  ### **C. Localization (Priority P1)**
69
69
  - **No Raw Strings**: All user-facing text MUST be localized.
70
- - **Implementation**:
71
- - Add keys to `lib/src/locale/locale_key.dart` (snake_case: `feature_screen_element`).
72
- - Update **ALL** translation files (`lib/src/locale/lang_en.dart`, `lib/src/locale/lang_ja.dart`). Optional modularization by feature:
73
- - `lib/src/locale/en/<feature>.dart`
74
- - `lib/src/locale/ja/<feature>.dart`
75
- - Aggregators `lang_en.dart` / `lang_ja.dart` merge feature maps to reduce conflicts when multiple tasks add keys while keeping `enUs` / `jaJp`.
76
- - **Usage**: Use `.tr` extension.
77
- - **Integration**: `lib/src/locale/translation_manager.dart` consumes `enUs` and `jaJp`. **Do not rename** these variables in aggregators.
78
- - **Common Keys**:
79
- - Create common modules: `lib/src/core/localization/en/common.dart` and `ja/common.dart`.
80
- - Use `common_*` keys for widespread phrases (Save/Cancel/OK/Back/Next/Loading/Error/Retry…).
81
- - When text matches common, **reuse** the common key instead of creating a feature key.
70
+ - **JSON Per Feature (Required)**:
71
+ - Each feature must have its own JSON files:
72
+ - `lib/src/locale/json/en/<feature>.json`
73
+ - `lib/src/locale/json/ja/<feature>.json`
74
+ - One JSON file equals one function/feature scope. Do not mix multiple features in one JSON.
75
+ - Keep key parity between `en` and `ja` for the same feature.
76
+ - **LocaleKey By Feature (Required)**:
77
+ - Split key declarations by feature:
78
+ - `lib/src/locale/keys/<feature>_locale_key.dart`
79
+ - `lib/src/locale/locale_key.dart` should act as barrel/aggregator entry for all feature key files.
80
+ - Key naming format remains snake_case with feature prefix: `feature_screen_element`.
81
+ - **Aggregator Contract (Required)**:
82
+ - `lang_en.dart` and `lang_ja.dart` are aggregators that merge feature maps from JSON-derived modules.
83
+ - Do not hardcode new feature strings directly inside `lang_en.dart`/`lang_ja.dart` except app-level bootstrap keys.
84
+ - `translation_manager.dart` must keep consuming `enUs` and `jaJp` variables.
85
+ - **Usage**: Use `.tr` extension with `LocaleKey`.
86
+ - **Common Keys**:
87
+ - Maintain `common` localization as separate JSON modules (`common.json`) per language.
88
+ - Reuse common keys (`ok`, `cancel`, `loading`, `error`, ...) instead of duplicating per feature.
82
89
  ```dart
83
90
  // ✅ Correct
84
91
  Text(LocaleKey.home_title.tr);
@@ -119,6 +126,16 @@ alwaysApply: false
119
126
  - **Rule**: Follow the [Assets Management Skill](../skills/flutter-assets-management/SKILL.md) strictly.
120
127
  - **Naming**: MUST match Figma layer names in `snake_case`.
121
128
  - **Registry**: All assets MUST be defined in `lib/src/utils/app_assets.dart`.
129
+ - **Feature-Scoped Assets (Required)**:
130
+ - Group assets by feature to avoid collisions/misuse:
131
+ - `assets/images/<feature>/...`
132
+ - `assets/images/icons/<feature>/...`
133
+ - Do not put new feature assets into generic/global folders unless truly shared.
134
+ - Shared cross-feature assets must live in explicit shared folder and use shared-prefixed constants.
135
+ - **Feature-Scoped AppAssets Constants (Required)**:
136
+ - Constant names must include feature prefix (for example: `homeDemoIconBellSvg`, `visitRecordImgHeaderPng`).
137
+ - Prefer splitting asset declarations by feature module, with `app_assets.dart` as aggregator entry.
138
+ - When refactoring, replace old ambiguous constants with feature-scoped names.
122
139
  - **Usage**:
123
140
  ```dart
124
141
  // ✅ Correct
@@ -129,9 +146,10 @@ alwaysApply: false
129
146
  ```
130
147
  - **Workflow**:
131
148
  1. Rename layer in Figma to `snake_case`.
132
- 2. Export to `assets/images/` or `assets/icons/`.
133
- 3. Register path in `AppAssets`.
134
- 4. Use `AppAssets.name` in code.
149
+ 2. Export to feature folder (`assets/images/<feature>/` or `assets/images/icons/<feature>/`).
150
+ 3. Register path with feature-scoped constant in `AppAssets`.
151
+ 4. Use `AppAssets.<featureScopedName>` in code.
152
+ 5. Remove/replace old ambiguous asset names and unused files.
135
153
  - **SVG Hygiene (Priority P1)**:
136
154
  - Flatten stroke → fill để tint/recolor an toàn.
137
155
  - Remove mask/clip/filter phức tạp; đảm bảo `viewBox` 24×24 cho icon.
@@ -188,8 +206,16 @@ Follow these steps when creating a new UI screen:
188
206
 
189
207
  ### **Step 5: Localization**
190
208
  - **Identify Strings**: List all text in the new UI (See [GetX Localization Skill](../skills/getx-localization-standard/SKILL.md)).
191
- - **Update Keys**: Add entries to `locale_key.dart` (e.g., `my_feature_title`).
192
- - **Update Translations**: Add translations to `lang_en.dart`, `lang_ja.dart`.
209
+ - **Create Feature JSON Files**:
210
+ - `lib/src/locale/json/en/<feature>.json`
211
+ - `lib/src/locale/json/ja/<feature>.json`
212
+ - **Update Keys**:
213
+ - Add/update keys in `lib/src/locale/keys/<feature>_locale_key.dart`.
214
+ - Ensure `locale_key.dart` exports/aggregates the feature key module.
215
+ - **Update Aggregators**:
216
+ - Merge new feature translations into `lang_en.dart` and `lang_ja.dart` via feature module maps.
217
+ - Ensure `enUs` and `jaJp` remain valid and synchronized.
218
+ - **Parity Check**: `en` and `ja` JSON for the same feature must have identical key sets.
193
219
 
194
220
  ### **Step 6: Main Page Implementation**
195
221
  - Create `<page_name>_page.dart` (See [UI Widgets Skill](../skills/flutter-ui-widgets/SKILL.md)).
@@ -281,6 +307,12 @@ Follow these steps when creating a new UI screen:
281
307
  ## **4. Checklist Before PR**
282
308
  - [ ] No hardcoded colors/styles?
283
309
  - [ ] No raw strings? (Localization used)
310
+ - [ ] **Localization JSON per feature created?** (`json/en/<feature>.json` + `json/ja/<feature>.json`)
311
+ - [ ] **LocaleKey split by feature?** (`keys/<feature>_locale_key.dart`)
312
+ - [ ] **EN/JA key parity passed?** (same keys between feature JSON files)
313
+ - [ ] **lang_en/lang_ja are aggregator-only?** (no random inline feature strings)
314
+ - [ ] **Assets grouped by feature?** (`assets/images/<feature>` and `assets/images/icons/<feature>`)
315
+ - [ ] **AppAssets constants are feature-scoped?** (no ambiguous generic names)
284
316
  - [ ] Used `App*` widgets where possible?
285
317
  - [ ] Page broken down into `components/`?
286
318
  - [ ] Logic separated into `interactor/`?
@@ -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';")