Add EveFlutter provider picker proof
This commit is contained in:
parent
2709061fd8
commit
e2e9b21e2f
6 changed files with 235 additions and 4 deletions
|
|
@ -280,6 +280,13 @@ Exit criteria:
|
|||
|
||||
Recently cut:
|
||||
|
||||
- EveFlutter now has a provider-catalog picker primitive. `EveProviderCatalog`
|
||||
parses conformance-export provider entries, including Sai/Norn/TeX plugin
|
||||
requirements, and `EveProviderPicker` selects those provider ids without
|
||||
reading web fixture paths. The Flutter smoke test proves provider selection
|
||||
from an export-shaped provider list, so the split-readiness report records the
|
||||
provider-picker proof as passed while leaving release/capture lifecycle
|
||||
ownership as the remaining EveFlutter blocker.
|
||||
- EveFlutter now has a conformance consumer smoke at
|
||||
`scripts/run-eveflutter-conformance-consumer-smoke.ps1`. It runs from
|
||||
`flutter/eve_parity`, consumes the exported conformance pack, and asserts the
|
||||
|
|
|
|||
|
|
@ -120,6 +120,11 @@ That script runs the export consumer from `flutter/eve_parity` and asserts the
|
|||
runtime pack, Sai provider fixture, Flutter runtime targets, and `EveFlutter`
|
||||
split target without reading `tools/parity/parity-manifest.json`.
|
||||
|
||||
The Flutter widget smoke also exercises `EveProviderCatalog` and
|
||||
`EveProviderPicker` against a conformance-export-shaped provider list. That is
|
||||
the runtime-side provider picker proof; it consumes provider entries and plugin
|
||||
requirements without reading web fixture paths.
|
||||
|
||||
Aetheria Unity package consumer smoke:
|
||||
|
||||
```powershell
|
||||
|
|
|
|||
|
|
@ -155,6 +155,9 @@ First cuts:
|
|||
- Scaffold a generic Eve Flutter app with the same provider picker as web.
|
||||
- Load the same local provider advertisements and local fixture surfaces during
|
||||
development.
|
||||
- Consume conformance-export provider entries through `EveProviderCatalog` and
|
||||
select them through `EveProviderPicker`; this is the split-ready provider
|
||||
picker input shape, distinct from web-local fallback fixture discovery.
|
||||
- Lower `surface.root` recursively into Flutter widgets/custom painters.
|
||||
- Lower CultUI partitions, `fieldRow`, labels, cards, buttons, metrics, and
|
||||
slider anatomy from `control.box`, `control.part`, and `control.hitArea`.
|
||||
|
|
|
|||
|
|
@ -122,6 +122,126 @@ class EveSurfaceState {
|
|||
}
|
||||
}
|
||||
|
||||
class EveProviderCatalog {
|
||||
const EveProviderCatalog({required this.providers});
|
||||
|
||||
final List<EveProviderEntry> providers;
|
||||
|
||||
factory EveProviderCatalog.fromConformanceExport(Map<String, dynamic> json) {
|
||||
final providers = ((json['providers'] as List<dynamic>?) ?? const [])
|
||||
.map((provider) {
|
||||
return EveProviderEntry.fromJson(provider as Map<String, dynamic>);
|
||||
})
|
||||
.toList(growable: false);
|
||||
return EveProviderCatalog(providers: providers);
|
||||
}
|
||||
|
||||
EveProviderEntry? findProvider(String providerId) {
|
||||
for (final provider in providers) {
|
||||
if (provider.providerId == providerId) return provider;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class EveProviderEntry {
|
||||
const EveProviderEntry({
|
||||
required this.providerId,
|
||||
required this.status,
|
||||
required this.ownerRepo,
|
||||
required this.advertisementPath,
|
||||
required this.surfaces,
|
||||
required this.commands,
|
||||
required this.pluginRequirements,
|
||||
});
|
||||
|
||||
final String providerId;
|
||||
final String status;
|
||||
final String ownerRepo;
|
||||
final String advertisementPath;
|
||||
final List<String> surfaces;
|
||||
final List<String> commands;
|
||||
final List<EvePluginRequirement> pluginRequirements;
|
||||
|
||||
factory EveProviderEntry.fromJson(Map<String, dynamic> json) {
|
||||
return EveProviderEntry(
|
||||
providerId: (json['providerId'] ?? '').toString(),
|
||||
status: (json['status'] ?? '').toString(),
|
||||
ownerRepo: (json['ownerRepo'] ?? '').toString(),
|
||||
advertisementPath: (json['advertisementPath'] ?? '').toString(),
|
||||
surfaces: _stringList(json['surfaces']),
|
||||
commands: _stringList(json['commands']),
|
||||
pluginRequirements:
|
||||
((json['pluginRequirements'] as List<dynamic>?) ?? const [])
|
||||
.map(
|
||||
(requirement) => EvePluginRequirement.fromJson(
|
||||
requirement as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EvePluginRequirement {
|
||||
const EvePluginRequirement({
|
||||
required this.surfaceId,
|
||||
required this.pluginId,
|
||||
required this.requiredCapabilities,
|
||||
});
|
||||
|
||||
final String surfaceId;
|
||||
final String pluginId;
|
||||
final List<String> requiredCapabilities;
|
||||
|
||||
factory EvePluginRequirement.fromJson(Map<String, dynamic> json) {
|
||||
return EvePluginRequirement(
|
||||
surfaceId: (json['surfaceId'] ?? '').toString(),
|
||||
pluginId: (json['pluginId'] ?? '').toString(),
|
||||
requiredCapabilities: _stringList(json['requiredCapabilities']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _stringList(Object? value) {
|
||||
return ((value as List<dynamic>?) ?? const [])
|
||||
.map((item) => item.toString())
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
class EveProviderPicker extends StatelessWidget {
|
||||
const EveProviderPicker({
|
||||
required this.catalog,
|
||||
required this.selectedProviderId,
|
||||
required this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final EveProviderCatalog catalog;
|
||||
final String selectedProviderId;
|
||||
final ValueChanged<String> onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButton<String>(
|
||||
value: catalog.findProvider(selectedProviderId) == null
|
||||
? null
|
||||
: selectedProviderId,
|
||||
hint: const Text('Select provider'),
|
||||
items: [
|
||||
for (final provider in catalog.providers)
|
||||
DropdownMenuItem<String>(
|
||||
value: provider.providerId,
|
||||
child: Text(provider.providerId),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) onSelected(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EveNode {
|
||||
EveNode({
|
||||
required this.id,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,49 @@ void main() {
|
|||
expect(intents.single.clientId, 'flutter-parity');
|
||||
expect(intents.single.payload['action']['operation'], 'focus_entity');
|
||||
});
|
||||
|
||||
testWidgets('provider picker consumes conformance export providers', (
|
||||
tester,
|
||||
) async {
|
||||
final catalog = EveProviderCatalog.fromConformanceExport(
|
||||
_providerCatalogExport,
|
||||
);
|
||||
var selected = 'aetheria';
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return EveProviderPicker(
|
||||
catalog: catalog,
|
||||
selectedProviderId: selected,
|
||||
onSelected: (providerId) {
|
||||
setState(() => selected = providerId);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(catalog.providers, hasLength(2));
|
||||
expect(catalog.findProvider('gamecult.home.vn')?.ownerRepo, 'Sai');
|
||||
expect(
|
||||
catalog
|
||||
.findProvider('gamecult.home.vn')
|
||||
?.pluginRequirements
|
||||
.map((requirement) => requirement.pluginId),
|
||||
containsAll(['sai.vn', 'norn.graph', 'tex.math']),
|
||||
);
|
||||
expect(find.text('aetheria'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(DropdownButton<String>));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('gamecult.home.vn').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(selected, 'gamecult.home.vn');
|
||||
});
|
||||
}
|
||||
|
||||
const fixtureId = String.fromEnvironment(
|
||||
|
|
@ -142,3 +185,48 @@ class _Viewport {
|
|||
final double width;
|
||||
final double height;
|
||||
}
|
||||
|
||||
const _providerCatalogExport = {
|
||||
'schema': 'gamecult.eve.conformance_export.v1',
|
||||
'providers': [
|
||||
{
|
||||
'providerId': 'aetheria',
|
||||
'status': 'advertised',
|
||||
'ownerRepo': 'Aetheria',
|
||||
'advertisementPath': 'web/fixtures/aetheria.provider-advertisement.json',
|
||||
'surfaces': ['aetheria.daemon.game'],
|
||||
'commands': ['aetheria.daemon.commands'],
|
||||
'pluginRequirements': [],
|
||||
},
|
||||
{
|
||||
'providerId': 'gamecult.home.vn',
|
||||
'status': 'advertised',
|
||||
'ownerRepo': 'Sai',
|
||||
'advertisementPath': 'web/fixtures/sai-vn.provider-advertisement.json',
|
||||
'surfaces': ['sai.visual_novel.surface'],
|
||||
'commands': ['story.choose', 'story.continue', 'story.jump'],
|
||||
'pluginRequirements': [
|
||||
{
|
||||
'surfaceId': 'sai.visual_novel.surface',
|
||||
'pluginId': 'sai.vn',
|
||||
'requiredCapabilities': [
|
||||
'vn.stage',
|
||||
'story.choose',
|
||||
'story.continue',
|
||||
'story.jump',
|
||||
],
|
||||
},
|
||||
{
|
||||
'surfaceId': 'sai.visual_novel.surface',
|
||||
'pluginId': 'norn.graph',
|
||||
'requiredCapabilities': ['embed.norn'],
|
||||
},
|
||||
{
|
||||
'surfaceId': 'sai.visual_novel.surface',
|
||||
'pluginId': 'tex.math',
|
||||
'requiredCapabilities': ['embed.tex'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -441,6 +441,15 @@
|
|||
}
|
||||
],
|
||||
"proofs": [
|
||||
{
|
||||
"description": "Provider picker consumes provider advertisements without Eve source-layout knowledge.",
|
||||
"status": "passed",
|
||||
"evidencePaths": [
|
||||
"flutter/eve_parity/lib/main.dart",
|
||||
"flutter/eve_parity/test/parity_smoke_test.dart",
|
||||
"tools/parity/run-parity.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Conformance pack can be consumed without depending on Eve's local harness layout.",
|
||||
"status": "passed",
|
||||
|
|
@ -452,7 +461,6 @@
|
|||
}
|
||||
],
|
||||
"pendingProofs": [
|
||||
"Provider picker consumes provider advertisements without Eve source-layout knowledge.",
|
||||
"Flutter build, release, and capture lifecycle can run from EveFlutter."
|
||||
]
|
||||
},
|
||||
|
|
@ -617,7 +625,7 @@
|
|||
"expectedSourceSymbols": [
|
||||
{
|
||||
"path": "flutter/eve_parity/lib/main.dart",
|
||||
"contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot"]
|
||||
"contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot", "EveProviderCatalog", "EveProviderPicker"]
|
||||
}
|
||||
],
|
||||
"commandTransportSmoke": {
|
||||
|
|
@ -668,7 +676,7 @@
|
|||
"expectedSourceSymbols": [
|
||||
{
|
||||
"path": "flutter/eve_parity/lib/main.dart",
|
||||
"contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot"]
|
||||
"contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot", "EveProviderCatalog", "EveProviderPicker"]
|
||||
}
|
||||
],
|
||||
"commandTransportSmoke": {
|
||||
|
|
@ -756,7 +764,7 @@
|
|||
"expectedSourceSymbols": [
|
||||
{
|
||||
"path": "flutter/eve_parity/lib/main.dart",
|
||||
"contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot"]
|
||||
"contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot", "EveProviderCatalog", "EveProviderPicker"]
|
||||
}
|
||||
],
|
||||
"commandTransportSmoke": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue