openuispec 0.2.10 → 0.2.12
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/README.md +3 -1
- package/check/index.ts +17 -0
- package/cli/index.ts +21 -3
- package/cli/init.ts +224 -10
- package/docs/cli.md +13 -8
- package/docs/file-formats.md +36 -0
- package/docs/implementation-notes.md +7 -0
- package/drift/index.ts +281 -40
- package/mcp-server/index.ts +179 -119
- package/mcp-server/screenshot.ts +19 -4
- package/package.json +5 -2
- package/prepare/index.ts +155 -18
- package/schema/openuispec.schema.json +59 -0
- package/schema/semantic-lint.ts +25 -1
- package/scripts/take-all-screenshots.ts +507 -0
- package/spec/openuispec-v0.1.md +13 -0
- package/status/index.ts +72 -2
- package/examples/social-app/.mcp.json +0 -10
- package/examples/social-app/AGENTS.md +0 -124
- package/examples/social-app/CLAUDE.md +0 -124
- package/examples/social-app/backend/.gitkeep +0 -1
- package/examples/social-app/generated/android/social-app/app/.paparazzi-hashes.json +0 -3
- package/examples/social-app/generated/android/social-app/app/build.gradle.kts +0 -94
- package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +0 -26
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +0 -20
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +0 -35
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +0 -13
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +0 -98
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +0 -19
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +0 -68
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +0 -15
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +0 -34
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +0 -390
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +0 -234
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +0 -641
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +0 -113
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +0 -212
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +0 -113
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +0 -137
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +0 -180
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +0 -169
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +0 -85
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +0 -74
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +0 -293
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +0 -116
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +0 -161
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +0 -164
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +0 -95
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +0 -123
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +0 -33
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +0 -41
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +0 -20
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +0 -82
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +0 -60
- package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +0 -9
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -5
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +0 -91
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +0 -10
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +0 -79
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +0 -79
- package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +0 -23
- package/examples/social-app/generated/android/social-app/app/src/test/kotlin/com/social/app/screenshots/HomeFeedScreenshotTest.kt +0 -34
- package/examples/social-app/generated/android/social-app/build.gradle.kts +0 -7
- package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +0 -50
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/examples/social-app/generated/android/social-app/gradle.properties +0 -11
- package/examples/social-app/generated/android/social-app/gradlew +0 -248
- package/examples/social-app/generated/android/social-app/settings.gradle.kts +0 -27
- package/examples/social-app/generated/web/social-app/index.html +0 -12
- package/examples/social-app/generated/web/social-app/package-lock.json +0 -2517
- package/examples/social-app/generated/web/social-app/package.json +0 -27
- package/examples/social-app/generated/web/social-app/src/app/App.tsx +0 -58
- package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +0 -259
- package/examples/social-app/generated/web/social-app/src/components/cards.tsx +0 -317
- package/examples/social-app/generated/web/social-app/src/components/ui.tsx +0 -340
- package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +0 -86
- package/examples/social-app/generated/web/social-app/src/i18n.tsx +0 -59
- package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +0 -85
- package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +0 -70
- package/examples/social-app/generated/web/social-app/src/lib/utils.ts +0 -97
- package/examples/social-app/generated/web/social-app/src/locales/en.json +0 -67
- package/examples/social-app/generated/web/social-app/src/locales/ru.json +0 -67
- package/examples/social-app/generated/web/social-app/src/locales/uz.json +0 -67
- package/examples/social-app/generated/web/social-app/src/main.tsx +0 -16
- package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +0 -90
- package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +0 -86
- package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +0 -57
- package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +0 -103
- package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +0 -52
- package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +0 -41
- package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +0 -115
- package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +0 -57
- package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +0 -76
- package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +0 -96
- package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +0 -79
- package/examples/social-app/generated/web/social-app/src/state/store.ts +0 -592
- package/examples/social-app/generated/web/social-app/src/styles.css +0 -125
- package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +0 -1
- package/examples/social-app/generated/web/social-app/tsconfig.json +0 -22
- package/examples/social-app/generated/web/social-app/tsconfig.node.json +0 -13
- package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +0 -1
- package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +0 -1
- package/examples/social-app/generated/web/social-app/vite.config.d.ts +0 -2
- package/examples/social-app/generated/web/social-app/vite.config.js +0 -6
- package/examples/social-app/generated/web/social-app/vite.config.ts +0 -7
- package/examples/social-app/package.json +0 -13
- package/examples/social-app/take-web-screenshots.ts +0 -97
- package/examples/taskflow/.codex/config.toml +0 -4
- package/examples/taskflow/.mcp.json +0 -10
- package/examples/taskflow/AGENTS.md +0 -124
- package/examples/taskflow/CLAUDE.md +0 -124
- package/examples/taskflow/backend/.gitkeep +0 -1
- package/examples/taskflow/generated/android/TaskFlow/README.md +0 -43
- package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +0 -76
- package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +0 -1
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +0 -21
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +0 -19
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +0 -283
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +0 -106
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +0 -57
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +0 -109
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +0 -112
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +0 -61
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +0 -82
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +0 -111
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +0 -77
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +0 -30
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +0 -86
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +0 -57
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +0 -155
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +0 -4
- package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +0 -5
- package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +0 -12
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/examples/taskflow/generated/android/TaskFlow/gradle.properties +0 -4
- package/examples/taskflow/generated/android/TaskFlow/gradlew +0 -18
- package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +0 -12
- package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +0 -18
- package/examples/taskflow/generated/ios/TaskFlow/README.md +0 -21
- package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +0 -115
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +0 -24
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +0 -150
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +0 -220
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +0 -122
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +0 -21
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +0 -201
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +0 -48
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +0 -59
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +0 -63
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +0 -85
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +0 -219
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +0 -320
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +0 -41
- package/examples/taskflow/generated/ios/TaskFlow/project.yml +0 -31
- package/examples/taskflow/generated/web/TaskFlow/README.md +0 -19
- package/examples/taskflow/generated/web/TaskFlow/index.html +0 -12
- package/examples/taskflow/generated/web/TaskFlow/package-lock.json +0 -1908
- package/examples/taskflow/generated/web/TaskFlow/package.json +0 -24
- package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +0 -58
- package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +0 -55
- package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +0 -82
- package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +0 -191
- package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +0 -41
- package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +0 -131
- package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +0 -25
- package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +0 -39
- package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +0 -111
- package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +0 -13
- package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +0 -111
- package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +0 -82
- package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +0 -132
- package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +0 -105
- package/examples/taskflow/generated/web/TaskFlow/src/store.ts +0 -216
- package/examples/taskflow/generated/web/TaskFlow/src/styles.css +0 -617
- package/examples/taskflow/generated/web/TaskFlow/src/types.ts +0 -64
- package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +0 -78
- package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +0 -21
- package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +0 -6
- package/examples/todo-orbit/.codex/config.toml +0 -4
- package/examples/todo-orbit/.mcp.json +0 -10
- package/examples/todo-orbit/AGENTS.md +0 -124
- package/examples/todo-orbit/CLAUDE.md +0 -124
- package/examples/todo-orbit/backend/.gitkeep +0 -1
- package/examples/todo-orbit/generated/android/Todo Orbit/README.md +0 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +0 -58
- package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +0 -1
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +0 -20
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +0 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +0 -345
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +0 -231
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +0 -169
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +0 -8
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +0 -236
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +0 -193
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +0 -102
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +0 -347
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +0 -347
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +0 -59
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +0 -149
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +0 -155
- package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +0 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +0 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +0 -248
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +0 -93
- package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +0 -18
- package/examples/todo-orbit/generated/ios/Todo Orbit/.screenshot-uitest/Sources/ScreenshotUITest.swift +0 -36
- package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +0 -29
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +0 -119
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +0 -119
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +0 -50
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +0 -204
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +0 -126
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +0 -70
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +0 -126
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +0 -61
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +0 -238
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +0 -94
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +0 -76
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +0 -364
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +0 -324
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +0 -439
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/xcshareddata/xcschemes/TodoOrbit.xcscheme +0 -89
- package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +0 -32
- package/examples/todo-orbit/generated/web/Todo Orbit/index.html +0 -16
- package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +0 -1087
- package/examples/todo-orbit/generated/web/Todo Orbit/package.json +0 -24
- package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +0 -2167
- package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +0 -13
- package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +0 -926
- package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +0 -19
- package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +0 -6
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ OpenUISpec includes an **MCP server** that AI assistants call automatically duri
|
|
|
74
74
|
openuispec init → configures MCP for your agent → AI calls tools automatically
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
When you ask your AI to "add a settings page" or "update the home feed," the MCP server provides spec context before generation, feeds authoritative spec contents during generation, and returns a
|
|
77
|
+
When you ask your AI to "add a settings page" or "update the home feed," the MCP server provides spec context before generation, feeds authoritative spec contents during generation, validates spec integrity after edits, and returns a spec-derived checklist for the AI to review the generated code against.
|
|
78
78
|
|
|
79
79
|
15 tools are available as both MCP tools and CLI commands — see the [full reference](./docs/cli.md).
|
|
80
80
|
|
|
@@ -90,6 +90,8 @@ When you ask your AI to "add a settings page" or "update the home feed," the MCP
|
|
|
90
90
|
| [Todo Orbit](./examples/todo-orbit/openuispec/) | iOS, Android, Web | Bilingual task app with localization, custom contracts |
|
|
91
91
|
| [Social App](./examples/social-app/openuispec/) | Android, Web | Trilingual social app with feeds, messaging, profiles |
|
|
92
92
|
|
|
93
|
+
Screenshots of the generated apps are in the [artifacts](./artifacts/) directory.
|
|
94
|
+
|
|
93
95
|
## Documentation
|
|
94
96
|
|
|
95
97
|
| Doc | What's in it |
|
package/check/index.ts
CHANGED
|
@@ -14,10 +14,13 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
14
14
|
import { join, resolve } from "node:path";
|
|
15
15
|
import YAML from "yaml";
|
|
16
16
|
import {
|
|
17
|
+
computeSharedDrift,
|
|
17
18
|
findProjectDir,
|
|
19
|
+
hasDriftChanges,
|
|
18
20
|
readManifest,
|
|
19
21
|
readProjectName,
|
|
20
22
|
resolveOutputDir,
|
|
23
|
+
sharedLayersForTarget,
|
|
21
24
|
} from "../drift/index.js";
|
|
22
25
|
import {
|
|
23
26
|
buildAjv,
|
|
@@ -153,6 +156,20 @@ function determinePrepare(
|
|
|
153
156
|
);
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
// Check for shared layer drift (only when tracks are configured)
|
|
160
|
+
const sharedLayers = sharedLayersForTarget(projectDir, target);
|
|
161
|
+
for (const layer of sharedLayers) {
|
|
162
|
+
if (layer.tracks.length === 0) continue;
|
|
163
|
+
const driftResult = computeSharedDrift(projectDir, layer);
|
|
164
|
+
if (driftResult.state !== null) {
|
|
165
|
+
if (hasDriftChanges(driftResult.drift)) {
|
|
166
|
+
warnings.push(
|
|
167
|
+
`Shared layer "${layer.name}" has spec drift — shared code may need updates before ${target} generation.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
156
173
|
const ready =
|
|
157
174
|
missing.length === 0 && backendContextReady && !pendingUserConfirmation;
|
|
158
175
|
|
package/cli/index.ts
CHANGED
|
@@ -95,6 +95,19 @@ function checkRulesVersion(): void {
|
|
|
95
95
|
|
|
96
96
|
// ── spec helpers (shared with MCP server) ────────────────────────────
|
|
97
97
|
|
|
98
|
+
function lookupLocaleKey(content: Record<string, unknown>, key: string): { found: boolean; value: unknown } {
|
|
99
|
+
if (key in content) return { found: true, value: content[key] };
|
|
100
|
+
const parts = key.split(".");
|
|
101
|
+
let current: unknown = content;
|
|
102
|
+
for (const part of parts) {
|
|
103
|
+
if (current === null || current === undefined || typeof current !== "object" || Array.isArray(current)) {
|
|
104
|
+
return { found: false, value: undefined };
|
|
105
|
+
}
|
|
106
|
+
current = (current as Record<string, unknown>)[part];
|
|
107
|
+
}
|
|
108
|
+
return current !== undefined ? { found: true, value: current } : { found: false, value: undefined };
|
|
109
|
+
}
|
|
110
|
+
|
|
98
111
|
function resolveSpecDir(projectDir: string, manifest: any, key: string): string {
|
|
99
112
|
return resolve(projectDir, manifest.includes?.[key] ?? `./${key}/`);
|
|
100
113
|
}
|
|
@@ -293,7 +306,10 @@ async function main(): Promise<void> {
|
|
|
293
306
|
const content = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
294
307
|
if (keys) {
|
|
295
308
|
const filtered: Record<string, unknown> = {};
|
|
296
|
-
for (const key of keys) {
|
|
309
|
+
for (const key of keys) {
|
|
310
|
+
const result = lookupLocaleKey(content, key);
|
|
311
|
+
if (result.found) filtered[key] = result.value;
|
|
312
|
+
}
|
|
297
313
|
console.log(JSON.stringify(filtered, null, 2));
|
|
298
314
|
} else {
|
|
299
315
|
console.log(JSON.stringify(content, null, 2));
|
|
@@ -332,6 +348,7 @@ async function main(): Promise<void> {
|
|
|
332
348
|
width: parseInt(getOption(rest, "--width") ?? "1280"),
|
|
333
349
|
height: parseInt(getOption(rest, "--height") ?? "800"),
|
|
334
350
|
},
|
|
351
|
+
scale: parseFloat(getOption(rest, "--scale") ?? "2"),
|
|
335
352
|
theme: getOption(rest, "--theme") as "light" | "dark" | undefined,
|
|
336
353
|
wait_for: parseInt(getOption(rest, "--wait-for") ?? "1000"),
|
|
337
354
|
full_page: getFlag(rest, "--full-page"),
|
|
@@ -382,6 +399,7 @@ async function main(): Promise<void> {
|
|
|
382
399
|
const result = await takeScreenshotBatch(cwd, {
|
|
383
400
|
captures,
|
|
384
401
|
viewport: config.viewport,
|
|
402
|
+
scale: parseFloat(getOption(rest, "--scale") ?? config.scale ?? "2"),
|
|
385
403
|
theme: (getOption(rest, "--theme") ?? config.theme) as "light" | "dark" | undefined,
|
|
386
404
|
output_dir: getOption(rest, "--output-dir") ?? config.output_dir ?? undefined,
|
|
387
405
|
});
|
|
@@ -459,14 +477,14 @@ Spec access:
|
|
|
459
477
|
openuispec spec-schema <type> Get full JSON schema for a spec type
|
|
460
478
|
|
|
461
479
|
Screenshots (single):
|
|
462
|
-
openuispec screenshot [--route /path] [--theme light|dark] [--output-dir dir]
|
|
480
|
+
openuispec screenshot [--route /path] [--width px] [--height px] [--scale n] [--theme light|dark] [--output-dir dir]
|
|
463
481
|
openuispec screenshot-android [--screen name] [--project-dir path] [--module name]
|
|
464
482
|
[--route deeplink] [--nav Step1,Step2] [--theme light|dark] [--output-dir dir]
|
|
465
483
|
openuispec screenshot-ios [--screen name] [--project-dir path] [--scheme name]
|
|
466
484
|
[--bundle-id id] [--device name] [--nav Step1,Step2] [--theme light|dark]
|
|
467
485
|
|
|
468
486
|
Screenshots (batch — build once, capture many):
|
|
469
|
-
openuispec screenshot-web-batch --config captures.json [--theme light|dark] [--output-dir dir]
|
|
487
|
+
openuispec screenshot-web-batch --config captures.json [--scale n] [--theme light|dark] [--output-dir dir]
|
|
470
488
|
openuispec screenshot-android-batch --config captures.json [--project-dir path]
|
|
471
489
|
[--module name] [--theme light|dark] [--output-dir dir]
|
|
472
490
|
openuispec screenshot-ios-batch --config captures.json [--project-dir path]
|
package/cli/init.ts
CHANGED
|
@@ -33,6 +33,7 @@ export type InitOptionsResponse = {
|
|
|
33
33
|
options?: string[];
|
|
34
34
|
}>;
|
|
35
35
|
configure_targets_note: string;
|
|
36
|
+
shared_layer_note?: string;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
export function listInitOptions(): InitOptionsResponse {
|
|
@@ -78,9 +79,17 @@ export function listInitOptions(): InitOptionsResponse {
|
|
|
78
79
|
type: "yes_no",
|
|
79
80
|
default: defaults.configureTargets,
|
|
80
81
|
},
|
|
82
|
+
{
|
|
83
|
+
key: "with_shared",
|
|
84
|
+
prompt: "Does the project share code between platforms (e.g. KMP commonMain)?",
|
|
85
|
+
type: "yes_no",
|
|
86
|
+
default: false,
|
|
87
|
+
},
|
|
81
88
|
],
|
|
82
89
|
configure_targets_note:
|
|
83
90
|
"If configure_targets is true, use `openuispec configure-target <target> --list-options` for each target after init to present stack choices to the user.",
|
|
91
|
+
shared_layer_note:
|
|
92
|
+
"If with_shared is true, add shared layer config to generation.shared in the manifest. Each shared layer needs: name, platforms (subset of targets), language, root (path relative to openuispec.yaml), tracks (spec categories: manifest, contracts, flows, screens, tokens, platform, locales), and scope (what code belongs there). Also add generation.structure entries for each target to define where platform-specific UI code goes and its scope.",
|
|
84
93
|
};
|
|
85
94
|
}
|
|
86
95
|
|
|
@@ -171,7 +180,12 @@ function getPackageVersion(): string {
|
|
|
171
180
|
function manifestTemplate(
|
|
172
181
|
name: string,
|
|
173
182
|
targets: string[],
|
|
174
|
-
options: {
|
|
183
|
+
options: {
|
|
184
|
+
withApi: boolean;
|
|
185
|
+
backendPath: string | null;
|
|
186
|
+
sharedLayers: SharedLayerAnswers[];
|
|
187
|
+
structures: StructureAnswers[];
|
|
188
|
+
}
|
|
175
189
|
): string {
|
|
176
190
|
const targetList = targets.join(", ");
|
|
177
191
|
const outputLines = targets
|
|
@@ -186,6 +200,38 @@ function manifestTemplate(
|
|
|
186
200
|
})
|
|
187
201
|
.join("\n");
|
|
188
202
|
|
|
203
|
+
function yamlPathsBlock(paths: Record<string, string>): string {
|
|
204
|
+
const entries = Object.entries(paths);
|
|
205
|
+
return entries.length > 0
|
|
206
|
+
? `\n paths:\n${entries.map(([k, v]) => ` ${k}: "${v}"`).join("\n")}`
|
|
207
|
+
: "";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let sharedBlock = "";
|
|
211
|
+
if (options.sharedLayers.length > 0) {
|
|
212
|
+
const layers = options.sharedLayers.map((layer) => {
|
|
213
|
+
const tracksLine = layer.tracks.length > 0
|
|
214
|
+
? `\n tracks: [${layer.tracks.join(", ")}]`
|
|
215
|
+
: "";
|
|
216
|
+
return ` ${layer.name}:
|
|
217
|
+
platforms: [${layer.platforms.join(", ")}]
|
|
218
|
+
language: ${layer.language}
|
|
219
|
+
root: "${layer.root}"
|
|
220
|
+
scope: "${layer.scope}"${tracksLine}${yamlPathsBlock(layer.paths)}`;
|
|
221
|
+
}).join("\n");
|
|
222
|
+
sharedBlock = ` shared:\n${layers}\n`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let structureBlock = "";
|
|
226
|
+
if (options.structures.length > 0) {
|
|
227
|
+
const entries = options.structures.map((s) => {
|
|
228
|
+
const scopeLine = s.scope ? `\n scope: "${s.scope}"` : "";
|
|
229
|
+
return ` ${s.target}:
|
|
230
|
+
root: "${s.root}"${scopeLine}${yamlPathsBlock(s.paths)}`;
|
|
231
|
+
}).join("\n");
|
|
232
|
+
structureBlock = ` structure:\n${entries}\n`;
|
|
233
|
+
}
|
|
234
|
+
|
|
189
235
|
return `# ${name} — OpenUISpec v0.1
|
|
190
236
|
spec_version: "0.1"
|
|
191
237
|
|
|
@@ -218,7 +264,7 @@ ${options.withApi ? ` code_roots:
|
|
|
218
264
|
backend: "${options.backendPath}" # Required when api.endpoints are declared
|
|
219
265
|
` : ""} output_format:
|
|
220
266
|
${outputLines}
|
|
221
|
-
|
|
267
|
+
${sharedBlock}${structureBlock}
|
|
222
268
|
data_model: {}
|
|
223
269
|
|
|
224
270
|
api:
|
|
@@ -348,21 +394,35 @@ Call these MCP tools directly. They return structured JSON with everything you n
|
|
|
348
394
|
|
|
349
395
|
**Pre-generation:**
|
|
350
396
|
1. Call \`openuispec_prepare\` with the target platform — returns spec context, platform config, constraints.
|
|
351
|
-
|
|
397
|
+
Use \`include_specs: true\` to embed all spec contents in one call (saves a separate read_specs).
|
|
398
|
+
2. Call \`openuispec_read_specs\` to load spec file contents if not using include_specs.
|
|
399
|
+
Without paths: returns file listing. With paths: returns contents. Use these as the AUTHORITATIVE source.
|
|
352
400
|
3. If spec changes are needed, update spec files FIRST, then call \`openuispec_check\`.
|
|
353
401
|
4. Generate or update the platform UI code based on the spec contents.
|
|
354
402
|
|
|
355
403
|
**Post-generation (EVERY TIME after writing UI code):**
|
|
356
|
-
5. Call \`openuispec_check\` to validate spec
|
|
357
|
-
|
|
358
|
-
|
|
404
|
+
5. Call \`openuispec_check\` to validate spec files (schema + semantics) and confirm prepare readiness.
|
|
405
|
+
Note: this validates the SPEC, not the generated code.
|
|
406
|
+
6. Call \`openuispec_check\` with \`audit: true\` to get a spec-derived checklist, then manually review
|
|
407
|
+
the generated code against it. For each screen, verify:
|
|
359
408
|
- Every field/action in the spec has a corresponding UI element
|
|
360
409
|
- Token values (colors, spacing, radii) match exactly — no approximations
|
|
361
410
|
- Contract \`must_handle\` states are all implemented (loading, error, empty, etc.)
|
|
362
411
|
- Adaptive breakpoints match the spec's \`size_classes\`
|
|
363
412
|
- Locale keys match \`$t:\` references
|
|
364
413
|
- Navigation targets match flow definitions
|
|
365
|
-
|
|
414
|
+
7. Report any real gaps found and fix them before finishing.
|
|
415
|
+
|
|
416
|
+
**Iterating before baseline:**
|
|
417
|
+
Generated code rarely needs just one pass. Read the spec, audit the generated code against it,
|
|
418
|
+
take screenshots to verify visuals, then fix gaps and repeat.
|
|
419
|
+
Multiple generate → review → fix cycles are expected before the user accepts the result.
|
|
420
|
+
|
|
421
|
+
**Baseline reminder:**
|
|
422
|
+
After generation, remind the user to review the output and run the baseline when satisfied:
|
|
423
|
+
> When you're happy with the generated output, run: \`openuispec drift --snapshot --target <t>\`
|
|
424
|
+
> This records the spec state so future changes are tracked as incremental drift.
|
|
425
|
+
Do not baseline on your own initiative — only run the snapshot when the user asks.
|
|
366
426
|
|
|
367
427
|
**Creating new spec files:**
|
|
368
428
|
- Call \`openuispec_spec_types\` to discover available spec types.
|
|
@@ -374,7 +434,7 @@ Call these MCP tools directly. They return structured JSON with everything you n
|
|
|
374
434
|
- \`openuispec_get_contract(name, variant?)\` — single contract, optionally one variant
|
|
375
435
|
- \`openuispec_get_tokens(category)\` — single token category (color, typography, spacing, etc.)
|
|
376
436
|
- \`openuispec_get_locale(locale, keys?)\` — single locale file, optionally filtered keys
|
|
377
|
-
- \`openuispec_check(target, screens?, contracts?)\` — scoped audit
|
|
437
|
+
- \`openuispec_check(target, audit?, screens?, contracts?)\` — validation + optional scoped audit checklist
|
|
378
438
|
|
|
379
439
|
Use \`read_specs\` for full-project generation; use focused getters when editing one screen or contract.
|
|
380
440
|
|
|
@@ -404,7 +464,7 @@ If MCP tools are not available, use these CLI commands with \`--json\` flag:
|
|
|
404
464
|
|
|
405
465
|
### Other CLI commands
|
|
406
466
|
- \`openuispec init\` — scaffold a new spec project
|
|
407
|
-
- \`openuispec drift --snapshot --target <t>\` — snapshot current state (
|
|
467
|
+
- \`openuispec drift --snapshot --target <t>\` — snapshot current state (user-initiated, after reviewing generated output)
|
|
408
468
|
- \`openuispec configure-target <t>\` — configure target platform stack
|
|
409
469
|
- \`openuispec update-rules\` — update AI rules to match installed package version
|
|
410
470
|
|
|
@@ -448,7 +508,8 @@ Read \`spec/openuispec-v0.1.md\` from the package first, then:
|
|
|
448
508
|
5. Fill in \`data_model\`, \`api.endpoints\` in \`${specDir}/openuispec.yaml\`
|
|
449
509
|
|
|
450
510
|
## Rules
|
|
451
|
-
- Do not
|
|
511
|
+
- Do not baseline on your own initiative — the user decides when generated output is accepted.
|
|
512
|
+
- After generation, always remind the user to review and baseline: \`openuispec drift --snapshot --target <t>\`.
|
|
452
513
|
- Do not modify generated UI without checking whether the spec must change first.
|
|
453
514
|
- Do not use \`configure-target --defaults\` as silent approval — ask the user to confirm.
|
|
454
515
|
- Always read spec format from the installed package, not from cached/memorized content.
|
|
@@ -664,6 +725,23 @@ export function extractRulesVersion(filePath: string): string | null {
|
|
|
664
725
|
|
|
665
726
|
export { getPackageVersion };
|
|
666
727
|
|
|
728
|
+
interface SharedLayerAnswers {
|
|
729
|
+
name: string;
|
|
730
|
+
platforms: string[];
|
|
731
|
+
language: string;
|
|
732
|
+
root: string;
|
|
733
|
+
tracks: string[];
|
|
734
|
+
scope: string;
|
|
735
|
+
paths: Record<string, string>;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
interface StructureAnswers {
|
|
739
|
+
target: string;
|
|
740
|
+
root: string;
|
|
741
|
+
scope: string;
|
|
742
|
+
paths: Record<string, string>;
|
|
743
|
+
}
|
|
744
|
+
|
|
667
745
|
interface InitOptions {
|
|
668
746
|
defaults: boolean;
|
|
669
747
|
quiet: boolean;
|
|
@@ -673,6 +751,7 @@ interface InitOptions {
|
|
|
673
751
|
withApi?: boolean;
|
|
674
752
|
backendPath?: string;
|
|
675
753
|
configureTargets?: boolean;
|
|
754
|
+
withShared?: boolean;
|
|
676
755
|
}
|
|
677
756
|
|
|
678
757
|
interface InitAnswers {
|
|
@@ -682,6 +761,8 @@ interface InitAnswers {
|
|
|
682
761
|
withApi: boolean;
|
|
683
762
|
backendPath: string | null;
|
|
684
763
|
configureTargets: boolean;
|
|
764
|
+
sharedLayers: SharedLayerAnswers[];
|
|
765
|
+
structures: StructureAnswers[];
|
|
685
766
|
}
|
|
686
767
|
|
|
687
768
|
function parseTargetsValue(raw: string): string[] {
|
|
@@ -737,6 +818,12 @@ function parseInitArgs(argv: string[]): InitOptions {
|
|
|
737
818
|
case "--no-configure-targets":
|
|
738
819
|
options.configureTargets = false;
|
|
739
820
|
break;
|
|
821
|
+
case "--with-shared":
|
|
822
|
+
options.withShared = true;
|
|
823
|
+
break;
|
|
824
|
+
case "--no-shared":
|
|
825
|
+
options.withShared = false;
|
|
826
|
+
break;
|
|
740
827
|
default:
|
|
741
828
|
if (arg.startsWith("--")) {
|
|
742
829
|
console.error(`Error: Unknown init option: ${arg}`);
|
|
@@ -758,9 +845,127 @@ function collectDefaults(): InitAnswers {
|
|
|
758
845
|
withApi: true,
|
|
759
846
|
backendPath: "../backend/",
|
|
760
847
|
configureTargets: true,
|
|
848
|
+
sharedLayers: [],
|
|
849
|
+
structures: [],
|
|
761
850
|
};
|
|
762
851
|
}
|
|
763
852
|
|
|
853
|
+
const SHARED_LAYER_DEFAULTS: Record<string, {
|
|
854
|
+
language: string;
|
|
855
|
+
root: string;
|
|
856
|
+
tracks: string[];
|
|
857
|
+
scope: string;
|
|
858
|
+
paths: Record<string, string>;
|
|
859
|
+
structureScope: Record<string, string>;
|
|
860
|
+
}> = {
|
|
861
|
+
kmp: {
|
|
862
|
+
language: "kotlin",
|
|
863
|
+
root: "../shared",
|
|
864
|
+
tracks: [],
|
|
865
|
+
scope: "Business logic, data models, repositories, API clients, view models/stores. No UI rendering.",
|
|
866
|
+
paths: { domain: "commonMain/domain/", features: "commonMain/features/" },
|
|
867
|
+
structureScope: {
|
|
868
|
+
ios: "Pure SwiftUI views and navigation. All business logic comes from the shared layer.",
|
|
869
|
+
android: "Pure Compose UI and navigation. All business logic comes from the shared layer.",
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
function defaultSharedConfig(targets: string[]): {
|
|
875
|
+
sharedLayers: SharedLayerAnswers[];
|
|
876
|
+
structures: StructureAnswers[];
|
|
877
|
+
} {
|
|
878
|
+
const mobilePlatforms = targets.filter((t) => t === "ios" || t === "android");
|
|
879
|
+
if (mobilePlatforms.length < 2) {
|
|
880
|
+
return { sharedLayers: [], structures: [] };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const preset = SHARED_LAYER_DEFAULTS.kmp;
|
|
884
|
+
const sharedLayers: SharedLayerAnswers[] = [{
|
|
885
|
+
name: "mobile_common",
|
|
886
|
+
platforms: mobilePlatforms,
|
|
887
|
+
language: preset.language,
|
|
888
|
+
root: preset.root,
|
|
889
|
+
tracks: preset.tracks,
|
|
890
|
+
scope: preset.scope,
|
|
891
|
+
paths: preset.paths,
|
|
892
|
+
}];
|
|
893
|
+
|
|
894
|
+
const structures: StructureAnswers[] = mobilePlatforms.map((t) => ({
|
|
895
|
+
target: t,
|
|
896
|
+
root: preset.root,
|
|
897
|
+
scope: preset.structureScope[t] ?? `Pure ${t} UI. Business logic comes from the shared layer.`,
|
|
898
|
+
paths: { ui: `${t}App/ui/` },
|
|
899
|
+
}));
|
|
900
|
+
|
|
901
|
+
return { sharedLayers, structures };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
async function collectSharedLayerAnswers(
|
|
905
|
+
rl: ReturnType<typeof createInterface>,
|
|
906
|
+
targets: string[],
|
|
907
|
+
): Promise<{ sharedLayers: SharedLayerAnswers[]; structures: StructureAnswers[] }> {
|
|
908
|
+
const withShared = await askYesNo(rl, "\nShare code between platforms (e.g. KMP commonMain)?", false);
|
|
909
|
+
if (!withShared) return { sharedLayers: [], structures: [] };
|
|
910
|
+
|
|
911
|
+
const defaults = defaultSharedConfig(targets);
|
|
912
|
+
const defaultLayer = defaults.sharedLayers[0];
|
|
913
|
+
if (!defaultLayer) return { sharedLayers: [], structures: [] };
|
|
914
|
+
|
|
915
|
+
console.log("\n Shared layer defaults (KMP):");
|
|
916
|
+
console.log(` platforms: ${defaultLayer.platforms.join(", ")}`);
|
|
917
|
+
console.log(` language: ${defaultLayer.language}`);
|
|
918
|
+
console.log(` root: ${defaultLayer.root}`);
|
|
919
|
+
console.log(` scope: ${defaultLayer.scope}`);
|
|
920
|
+
|
|
921
|
+
const useDefaults = await askYesNo(rl, " Use these defaults?", true);
|
|
922
|
+
if (useDefaults) return defaults;
|
|
923
|
+
|
|
924
|
+
const layerName = await ask(rl, " Shared layer name", defaultLayer.name);
|
|
925
|
+
const platformsRaw = await askList(rl, " Platforms", targets, defaultLayer.platforms);
|
|
926
|
+
const language = await ask(rl, " Language", defaultLayer.language);
|
|
927
|
+
const root = await ask(rl, " Root path (relative to openuispec.yaml)", defaultLayer.root);
|
|
928
|
+
const scope = await ask(rl, " Scope (what code belongs here)", defaultLayer.scope);
|
|
929
|
+
const wantTracks = await askYesNo(rl, " Enable hash-based drift tracking for this layer?", false);
|
|
930
|
+
const tracksRaw = wantTracks
|
|
931
|
+
? await askList(
|
|
932
|
+
rl,
|
|
933
|
+
" Tracked spec categories",
|
|
934
|
+
["manifest", "tokens", "contracts", "screens", "flows", "platform", "locales"],
|
|
935
|
+
["manifest", "contracts", "flows"]
|
|
936
|
+
)
|
|
937
|
+
: [];
|
|
938
|
+
|
|
939
|
+
const sharedLayers: SharedLayerAnswers[] = [{
|
|
940
|
+
name: layerName,
|
|
941
|
+
platforms: platformsRaw,
|
|
942
|
+
language,
|
|
943
|
+
root,
|
|
944
|
+
tracks: tracksRaw,
|
|
945
|
+
scope,
|
|
946
|
+
paths: defaultLayer.paths,
|
|
947
|
+
}];
|
|
948
|
+
|
|
949
|
+
const structures: StructureAnswers[] = [];
|
|
950
|
+
for (const t of platformsRaw) {
|
|
951
|
+
const defaultStructure = defaults.structures.find((s) => s.target === t);
|
|
952
|
+
const structRoot = await ask(rl, ` ${t} structure root`, defaultStructure?.root ?? root);
|
|
953
|
+
const structScope = await ask(
|
|
954
|
+
rl,
|
|
955
|
+
` ${t} scope (what code belongs in the ${t} target)`,
|
|
956
|
+
defaultStructure?.scope ?? `Pure ${t} UI rendering.`
|
|
957
|
+
);
|
|
958
|
+
structures.push({
|
|
959
|
+
target: t,
|
|
960
|
+
root: structRoot,
|
|
961
|
+
scope: structScope,
|
|
962
|
+
paths: defaultStructure?.paths ?? { ui: `${t}App/ui/` },
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return { sharedLayers, structures };
|
|
967
|
+
}
|
|
968
|
+
|
|
764
969
|
async function collectInteractiveAnswers(rl: ReturnType<typeof createInterface>): Promise<InitAnswers> {
|
|
765
970
|
const defaults = collectDefaults();
|
|
766
971
|
const name = await ask(rl, "Project name", defaults.name);
|
|
@@ -777,6 +982,7 @@ async function collectInteractiveAnswers(rl: ReturnType<typeof createInterface>)
|
|
|
777
982
|
? await ask(rl, "Backend folder path relative to openuispec.yaml", defaults.backendPath ?? "../backend/")
|
|
778
983
|
: null;
|
|
779
984
|
const configureTargets = await askYesNo(rl, "Configure target stacks now?", defaults.configureTargets);
|
|
985
|
+
const { sharedLayers, structures } = await collectSharedLayerAnswers(rl, targets);
|
|
780
986
|
|
|
781
987
|
return {
|
|
782
988
|
name,
|
|
@@ -785,6 +991,8 @@ async function collectInteractiveAnswers(rl: ReturnType<typeof createInterface>)
|
|
|
785
991
|
withApi,
|
|
786
992
|
backendPath,
|
|
787
993
|
configureTargets,
|
|
994
|
+
sharedLayers,
|
|
995
|
+
structures,
|
|
788
996
|
};
|
|
789
997
|
}
|
|
790
998
|
|
|
@@ -808,6 +1016,8 @@ function collectNonInteractiveAnswers(argv: string[]): InitAnswers {
|
|
|
808
1016
|
|
|
809
1017
|
const withApi = parsed.withApi ?? defaults.withApi;
|
|
810
1018
|
const backendPath = withApi ? parsed.backendPath ?? defaults.backendPath : null;
|
|
1019
|
+
const withShared = parsed.withShared ?? false;
|
|
1020
|
+
const { sharedLayers, structures } = withShared ? defaultSharedConfig(targets) : { sharedLayers: [], structures: [] };
|
|
811
1021
|
|
|
812
1022
|
return {
|
|
813
1023
|
name: parsed.name ?? defaults.name,
|
|
@@ -816,6 +1026,8 @@ function collectNonInteractiveAnswers(argv: string[]): InitAnswers {
|
|
|
816
1026
|
withApi,
|
|
817
1027
|
backendPath,
|
|
818
1028
|
configureTargets: parsed.configureTargets ?? defaults.configureTargets,
|
|
1029
|
+
sharedLayers,
|
|
1030
|
+
structures,
|
|
819
1031
|
};
|
|
820
1032
|
}
|
|
821
1033
|
|
|
@@ -864,6 +1076,8 @@ export async function init(argv: string[] = []): Promise<void> {
|
|
|
864
1076
|
manifestTemplate(answers.name, answers.targets, {
|
|
865
1077
|
withApi: answers.withApi,
|
|
866
1078
|
backendPath: answers.backendPath,
|
|
1079
|
+
sharedLayers: answers.sharedLayers,
|
|
1080
|
+
structures: answers.structures,
|
|
867
1081
|
}),
|
|
868
1082
|
quiet
|
|
869
1083
|
);
|
package/docs/cli.md
CHANGED
|
@@ -41,12 +41,12 @@ Or run directly: `openuispec mcp`
|
|
|
41
41
|
| Tool | When | What it does |
|
|
42
42
|
|------|------|-------------|
|
|
43
43
|
| `openuispec_spec_types` | Before creating spec files | Lists all available spec types with descriptions |
|
|
44
|
-
| `openuispec_spec_schema` | Before creating/editing spec files | Returns
|
|
45
|
-
| `openuispec_prepare` | Before UI code generation | Returns spec context, platform config,
|
|
46
|
-
| `openuispec_read_specs` | Before and after generation |
|
|
47
|
-
| `openuispec_check` | After generation |
|
|
44
|
+
| `openuispec_spec_schema` | Before creating/editing spec files | Returns JSON schema for a spec type. Optional `summary` for top-level overview |
|
|
45
|
+
| `openuispec_prepare` | Before UI code generation | Returns spec context, platform config, constraints. Optional `include_specs` embeds all spec contents |
|
|
46
|
+
| `openuispec_read_specs` | Before and after generation | Without `paths`: returns file listing. With `paths`: loads spec contents |
|
|
47
|
+
| `openuispec_check` | After spec edits or generation | Spec validation (schema + semantic) + prepare readiness. `audit=true` returns a spec-derived checklist for manual code review |
|
|
48
48
|
| `openuispec_validate` | After spec edits | Schema-only validation, optionally filtered by group |
|
|
49
|
-
| `openuispec_drift` | Before updates | Detect
|
|
49
|
+
| `openuispec_drift` | Before updates / after generation | Detect drift, or `snapshot=true` to create/update baseline |
|
|
50
50
|
| `openuispec_status` | Anytime | Cross-target summary: baselines, drift, next steps |
|
|
51
51
|
| `openuispec_get_screen` | Incremental edits | Get a single screen spec by name |
|
|
52
52
|
| `openuispec_get_contract` | Incremental edits | Get a single contract spec, optionally filtered to one variant |
|
|
@@ -55,6 +55,9 @@ Or run directly: `openuispec mcp`
|
|
|
55
55
|
| `openuispec_screenshot` | Visual verification | Screenshot the web app at a route via headless browser |
|
|
56
56
|
| `openuispec_screenshot_android` | Visual verification | Screenshot Android app on emulator. Works with any project via `project_dir` |
|
|
57
57
|
| `openuispec_screenshot_ios` | Visual verification | Screenshot iOS app on Simulator via XCUITest. Works with any project via `project_dir` |
|
|
58
|
+
| `openuispec_screenshot_web_batch` | Visual verification | Multiple web screenshots in one server session |
|
|
59
|
+
| `openuispec_screenshot_android_batch` | Visual verification | Multiple Android screenshots in one build+install cycle |
|
|
60
|
+
| `openuispec_screenshot_ios_batch` | Visual verification | Multiple iOS screenshots in one build+install cycle |
|
|
58
61
|
|
|
59
62
|
The server includes **protocol-level instructions** that trigger on UI-related requests independently of CLAUDE.md rules.
|
|
60
63
|
|
|
@@ -93,12 +96,12 @@ openuispec spec-schema <type> # Get JSON schema for a spec type
|
|
|
93
96
|
|
|
94
97
|
```bash
|
|
95
98
|
# Single captures
|
|
96
|
-
openuispec screenshot --route /home [--theme dark] [--output-dir dir]
|
|
99
|
+
openuispec screenshot --route /home [--width 1280] [--height 800] [--scale 2] [--theme dark] [--output-dir dir]
|
|
97
100
|
openuispec screenshot-android [--project-dir path] [--screen name] [--module name] [--route deeplink]
|
|
98
101
|
openuispec screenshot-ios [--project-dir path] [--screen name] [--scheme name] [--bundle-id id]
|
|
99
102
|
|
|
100
103
|
# Batch — build once, capture many
|
|
101
|
-
openuispec screenshot-web-batch --config captures.json [--theme dark] [--output-dir dir]
|
|
104
|
+
openuispec screenshot-web-batch --config captures.json [--scale 2] [--theme dark] [--output-dir dir]
|
|
102
105
|
openuispec screenshot-android-batch --config captures.json [--project-dir path] [--module name]
|
|
103
106
|
openuispec screenshot-ios-batch --config captures.json [--project-dir path] [--scheme name] [--bundle-id id]
|
|
104
107
|
```
|
|
@@ -113,6 +116,7 @@ Screenshot tools work with **any** project — use `--project-dir` to skip manif
|
|
|
113
116
|
| `--bundle-id` | -- | yes | Override bundle ID (default: auto-detect) |
|
|
114
117
|
| `--route` | yes | -- | Deep link URI for navigation |
|
|
115
118
|
| `--nav` | yes | yes | UI tap steps, comma-separated |
|
|
119
|
+
| `--scale` | web | -- | Device pixel ratio for sharper screenshots (default: 2) |
|
|
116
120
|
| `--theme` | yes | yes | Force light or dark mode |
|
|
117
121
|
| `--device` | -- | yes | Simulator device name |
|
|
118
122
|
| `--output-dir` | yes | yes | Save screenshot to directory |
|
|
@@ -125,6 +129,7 @@ All batch commands accept `--config captures.json`. The JSON file has the same s
|
|
|
125
129
|
{
|
|
126
130
|
"project_dir": "path/to/project",
|
|
127
131
|
"output_dir": "screenshots",
|
|
132
|
+
"scale": 2,
|
|
128
133
|
"theme": "light",
|
|
129
134
|
"captures": [
|
|
130
135
|
{ "screen": "home", "route": "/home", "wait_for": 3000 },
|
|
@@ -156,5 +161,5 @@ openuispec drift --snapshot --target ios
|
|
|
156
161
|
```
|
|
157
162
|
|
|
158
163
|
- `prepare` runs in `bootstrap` mode for first-time generation and `update` mode after a snapshot exists
|
|
159
|
-
- `drift --snapshot` is bookkeeping — it does not prove code matches the spec, and requires the output directory to exist
|
|
164
|
+
- `drift --snapshot` is bookkeeping — it does not prove code matches the spec, and requires the output directory to exist. Only run it after reviewing the generated output.
|
|
160
165
|
- Run `openuispec status` between targets to see what still needs updating
|
package/docs/file-formats.md
CHANGED
|
@@ -64,6 +64,42 @@ Paths are relative to `openuispec.yaml`. The `.openuispec-state.json` file recor
|
|
|
64
64
|
- `generation.extra_rules` can hold project-wide generation conventions
|
|
65
65
|
- `drift --snapshot` requires that target output directory to already exist
|
|
66
66
|
|
|
67
|
+
## Shared code layers
|
|
68
|
+
|
|
69
|
+
Projects that share business logic between platforms (e.g. KMP `commonMain`) can declare `generation.shared` to tell AI what code belongs in the shared layer vs platform-specific targets:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
generation:
|
|
73
|
+
targets: [ios, android, web]
|
|
74
|
+
shared:
|
|
75
|
+
mobile_common:
|
|
76
|
+
platforms: [ios, android]
|
|
77
|
+
language: kotlin
|
|
78
|
+
root: "../shared"
|
|
79
|
+
scope: "Business logic, data models, repositories, API clients, view models. No UI rendering."
|
|
80
|
+
# tracks: [manifest, contracts] # optional — enables hash-based drift detection for this layer
|
|
81
|
+
paths:
|
|
82
|
+
domain: "commonMain/domain/"
|
|
83
|
+
features: "commonMain/features/"
|
|
84
|
+
structure:
|
|
85
|
+
ios:
|
|
86
|
+
root: "../shared"
|
|
87
|
+
scope: "Pure SwiftUI views and navigation. All business logic comes from the shared layer."
|
|
88
|
+
paths:
|
|
89
|
+
ui: "iosApp/ui/"
|
|
90
|
+
android:
|
|
91
|
+
root: "../shared"
|
|
92
|
+
scope: "Pure Compose UI and navigation. All business logic comes from the shared layer."
|
|
93
|
+
paths:
|
|
94
|
+
ui: "androidApp/ui/"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **`scope`** (required on shared, optional on structure) — tells AI what code belongs where. This is the primary mechanism for routing generation work between shared and platform layers.
|
|
98
|
+
- **`tracks`** (optional) — when set, enables hash-based drift detection scoped to specific spec categories (`manifest`, `tokens`, `contracts`, `screens`, `flows`, `platform`, `locales`). When omitted, the shared layer relies on `scope` alone.
|
|
99
|
+
- **`structure`** — when present, overrides the heuristic code root discovery for a target. Paths are relative to `root`.
|
|
100
|
+
- Shared layers are not targets — they are tracked alongside targets in `prepare` and `status` output.
|
|
101
|
+
- `openuispec init --with-shared` scaffolds KMP defaults when both ios and android targets are selected.
|
|
102
|
+
|
|
67
103
|
## Spec sections overview
|
|
68
104
|
|
|
69
105
|
| Section | What it defines |
|
|
@@ -87,6 +87,13 @@
|
|
|
87
87
|
- backend generation context
|
|
88
88
|
- if the manifest declares `api.endpoints`, `generation.code_roots.backend` is required
|
|
89
89
|
- `prepare` should surface the resolved backend root so AI can inspect backend code when generating API clients
|
|
90
|
+
- Shared code layers (`generation.shared`):
|
|
91
|
+
- when configured, `prepare` includes `shared_layers` in its output with per-layer `scope`, `already_generated`, and `guidance`
|
|
92
|
+
- `scope` tells AI what code belongs in the shared layer vs the platform target — this is the primary routing mechanism
|
|
93
|
+
- optional `tracks` enables hash-based drift detection scoped to specific spec categories
|
|
94
|
+
- `generation.structure` overrides heuristic code root discovery when present, and its `scope` field tells AI what goes in the platform-specific target
|
|
95
|
+
- `suggestCodeRoots` includes shared layer roots and structure paths alongside target output directories
|
|
96
|
+
- generation rules include shared layer and target scope descriptions
|
|
90
97
|
- Important positioning:
|
|
91
98
|
- `prepare` does not generate code
|
|
92
99
|
- `prepare` does not verify code correctness
|