Add Eve web provider picker
This commit is contained in:
parent
ae01487c06
commit
83ad5bcb7d
6 changed files with 1066 additions and 72 deletions
|
|
@ -12,7 +12,7 @@ Android/Kotlin. Repixelizer is the first style-parity target.
|
|||
|
||||
| Renderer | Status | Proof | Gap |
|
||||
| --- | --- | --- | --- |
|
||||
| Web reference | First generic app target | `web/` renders live VoidBot and fixture surfaces | Needs provider picker, Repixelizer fixture, visual regression fixtures, and command round-trip tests |
|
||||
| Web reference | First generic app target | `web/` renders live VoidBot and fixture surfaces through the provider picker | Needs visual regression fixtures and command round-trip tests |
|
||||
| iOS / UIKit | Native proof | Renders Odin's fullscreen interface wall from `surface.root`, generic provider trees, and a custom VoidBot cockpit with avatar images | Needs provider picker, Repixelizer style token lowering, and visual parity fixtures |
|
||||
| Android / Kotlin | Device-edge proof | APK builds and shows broker/sensor status | Needs provider picker, full surface tree rendering, style token lowering, asset image lowering, and command controls |
|
||||
| Fensalir Direct2D | Documented landing zone | Existing `AquariumUiDocument` and `DirectWriteOverlay` path | Needs adapter from Eve surface document to `AquariumUiDocument` and DirectWrite/Direct2D token lowering |
|
||||
|
|
@ -20,19 +20,22 @@ Android/Kotlin. Repixelizer is the first style-parity target.
|
|||
|
||||
## Spawned Surfaces
|
||||
|
||||
The browser reference runner currently exposes hardcoded fixture/live tabs:
|
||||
The browser reference runner now exposes local advertisements through the
|
||||
provider picker:
|
||||
|
||||
- `VoidBot Live`: connects to Mimir's `/eve/deck` broker and opens
|
||||
`voidbot.swarm`.
|
||||
- `Repixelizer`: loads `web/fixtures/repixelizer.provider-advertisement.json`
|
||||
and `web/fixtures/repixelizer.eve-surface.json`.
|
||||
- `Fensalir Direct2D`: loads `web/fixtures/fensalir-client-surface.json`, a
|
||||
recorded surface describing the Direct2D client lowering path.
|
||||
- `Sai VN Surface`: loads `web/fixtures/sai-vn-surface.json`.
|
||||
- `Huginn .cc`: compiles `web/fixtures/huginn-cc-surface.eve`.
|
||||
- `Reactive DSL`: compiles `web/fixtures/reactive-composition.eve`.
|
||||
|
||||
Those tabs are temporary probes. The runtime app target is a provider picker
|
||||
fed by `gamecult.eve.provider_advertisement.v1`, with fixtures represented as
|
||||
local advertisements.
|
||||
The remaining temporary part is the local provider list itself. The runtime app
|
||||
target is the same picker fed by live `gamecult.eve.provider_advertisement.v1`
|
||||
documents through CultMesh/Odin.
|
||||
|
||||
Start it with:
|
||||
|
||||
|
|
|
|||
558
web/fixtures/repixelizer.eve-surface.json
Normal file
558
web/fixtures/repixelizer.eve-surface.json
Normal file
|
|
@ -0,0 +1,558 @@
|
|||
{
|
||||
"commands": [
|
||||
{
|
||||
"authority": "browser-lowering",
|
||||
"command": "repixelizer.web.open",
|
||||
"method": "GET",
|
||||
"route": "/app/",
|
||||
"surfaceId": "repixelizer.operator.surface",
|
||||
"transport": "http"
|
||||
},
|
||||
{
|
||||
"authority": "repixelizer-route-policy",
|
||||
"command": "repixelizer.job.submit",
|
||||
"method": "POST",
|
||||
"route": "/api/jobs",
|
||||
"surfaceId": "repixelizer.operator.surface",
|
||||
"transport": "http"
|
||||
},
|
||||
{
|
||||
"authority": "repixelizer-job-owner",
|
||||
"command": "repixelizer.job.cancel_own",
|
||||
"method": "DELETE",
|
||||
"route": "/api/jobs/{job_id}",
|
||||
"surfaceId": "repixelizer.operator.surface",
|
||||
"transport": "http"
|
||||
}
|
||||
],
|
||||
"loweringComparison": {
|
||||
"periwinkleAndroidKotlin": {
|
||||
"keeps": [
|
||||
"surface root traversal",
|
||||
"pane/card/text hierarchy",
|
||||
"TextView body content",
|
||||
"basic ProgressBar metric rendering",
|
||||
"node-bound command taps"
|
||||
],
|
||||
"losesUntilRendererParity": [
|
||||
"Press Start 2P and VT323 font selection",
|
||||
"Repixelizer shell frame, scanlines, pixel grid, corner accents, and chunky border widths",
|
||||
"landing/app image assets; current Android lowerer prints asset refs when images are not explicitly implemented",
|
||||
"upload picker, comparison canvas, pan/zoom inspection, eyedropper, and paint controls",
|
||||
"provider style tokens; current Kotlin toneColor/textColorFor/textSizeFor are hard-coded"
|
||||
],
|
||||
"minimumParityCuts": [
|
||||
"map surface.styles.tokens before toneColor/textColorFor/textSizeFor",
|
||||
"add packaged pixel fonts or font download cache",
|
||||
"render image assetUri/assetRef as ImageView with pixelated sampling",
|
||||
"lower command controls to Android upload/document picker and job route calls"
|
||||
],
|
||||
"source": "E:\\Projects\\Eve\\android\\app\\src\\main\\java\\org\\gamecult\\eve\\MainActivity.kt",
|
||||
"status": "surface-tree-renderer-present-style-token-gap"
|
||||
}
|
||||
},
|
||||
"providerId": "repixelizer",
|
||||
"providerKind": "repixelizer.product",
|
||||
"runtime": {
|
||||
"auth": {
|
||||
"appSlug": "repixelizer",
|
||||
"enabled": false,
|
||||
"loginUrl": null,
|
||||
"logoutUrl": null,
|
||||
"mode": "off",
|
||||
"protectQueue": false,
|
||||
"required": false
|
||||
},
|
||||
"hostedDemo": false,
|
||||
"limits": {
|
||||
"defaultSteps": 48,
|
||||
"heartbeatIntervalSeconds": 10,
|
||||
"maxInputDimension": 4096,
|
||||
"maxOutputDimension": 1024,
|
||||
"maxSteps": 256,
|
||||
"maxUploadBytes": 16777216,
|
||||
"queueCapacity": 32,
|
||||
"staleAfterSeconds": 30
|
||||
},
|
||||
"queue": {
|
||||
"activeStatus": null,
|
||||
"hasActiveJob": false,
|
||||
"queueCapacity": 32,
|
||||
"queueDepth": 0,
|
||||
"waitingCount": 0
|
||||
}
|
||||
},
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"surface": {
|
||||
"id": "repixelizer.operator.surface",
|
||||
"root": {
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.hero.copy",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "Force fake pixel art back onto a real grid, then inspect each solver step without sanding off the crunchy old-machine charm."
|
||||
},
|
||||
"role": "body",
|
||||
"text": "Force fake pixel art back onto a real grid, then inspect each solver step without sanding off the crunchy old-machine charm."
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.hero.open.body",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "Browser lowering remains the full interactive app until native Eve gains upload and canvas editing parity."
|
||||
},
|
||||
"role": "body",
|
||||
"text": "Browser lowering remains the full interactive app until native Eve gains upload and canvas editing parity."
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.hero.open.route",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"method": "GET",
|
||||
"role": "mono",
|
||||
"route": "/app/",
|
||||
"text": "GET /app/"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "GET /app/"
|
||||
}
|
||||
],
|
||||
"commandId": "repixelizer.web.open",
|
||||
"id": "repixelizer.hero.open",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"commandId": "repixelizer.web.open",
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Open demo",
|
||||
"transport": {
|
||||
"kind": "http",
|
||||
"method": "GET",
|
||||
"route": "/app/"
|
||||
}
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "cool",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Open demo"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.hero",
|
||||
"kind": "pane",
|
||||
"layout": {
|
||||
"direction": "vertical",
|
||||
"padding": 10
|
||||
},
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Repixelizer hosted demo"
|
||||
},
|
||||
"role": "section",
|
||||
"style": {
|
||||
"tone": "warm",
|
||||
"variant": "repixelizer-retro-pane"
|
||||
},
|
||||
"text": "Repixelizer hosted demo"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.workflow.submit.body",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "Submit a PNG/JPEG/WebP/GIF into the GUI job queue; queue ownership stays in repixelizer.gui."
|
||||
},
|
||||
"role": "body",
|
||||
"text": "Submit a PNG/JPEG/WebP/GIF into the GUI job queue; queue ownership stays in repixelizer.gui."
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.workflow.submit.route",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"method": "POST",
|
||||
"role": "mono",
|
||||
"route": "/api/jobs",
|
||||
"text": "POST /api/jobs"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "POST /api/jobs"
|
||||
}
|
||||
],
|
||||
"commandId": "repixelizer.job.submit",
|
||||
"id": "repixelizer.workflow.submit",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"commandId": "repixelizer.job.submit",
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Upload image",
|
||||
"transport": {
|
||||
"kind": "http",
|
||||
"method": "POST",
|
||||
"route": "/api/jobs"
|
||||
}
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "cool",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Upload image"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.inspect.source",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "source, edge map, lattice overlay, phase field, cleaned output"
|
||||
},
|
||||
"role": "body",
|
||||
"text": "source, edge map, lattice overlay, phase field, cleaned output"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.inspect.canvas",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "comparison canvas and editor lower to native only after Periwinkle gets image/control parity"
|
||||
},
|
||||
"role": "body",
|
||||
"text": "comparison canvas and editor lower to native only after Periwinkle gets image/control parity"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.workflow.inspect",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Inspect solver"
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "panel",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Inspect solver"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.workflow.cancel.body",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "The command intent delegates to the existing owner-bound DELETE route."
|
||||
},
|
||||
"role": "body",
|
||||
"text": "The command intent delegates to the existing owner-bound DELETE route."
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.workflow.cancel.route",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"method": "DELETE",
|
||||
"role": "mono",
|
||||
"route": "/api/jobs/{job_id}",
|
||||
"text": "DELETE /api/jobs/{job_id}"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "DELETE /api/jobs/{job_id}"
|
||||
}
|
||||
],
|
||||
"commandId": "repixelizer.job.cancel_own",
|
||||
"id": "repixelizer.workflow.cancel",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"commandId": "repixelizer.job.cancel_own",
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Cancel own job",
|
||||
"transport": {
|
||||
"kind": "http",
|
||||
"method": "DELETE",
|
||||
"route": "/api/jobs/{job_id}"
|
||||
}
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "cool",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Cancel own job"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.workflow",
|
||||
"kind": "pane",
|
||||
"layout": {
|
||||
"direction": "vertical",
|
||||
"padding": 10
|
||||
},
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Single page app"
|
||||
},
|
||||
"role": "section",
|
||||
"style": {
|
||||
"tone": "warm",
|
||||
"variant": "repixelizer-retro-pane"
|
||||
},
|
||||
"text": "Single page app"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.runtime.hosted",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "hostedDemo: False"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "hostedDemo: False"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.runtime.steps",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "defaultSteps: 48 maxSteps: 256"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "defaultSteps: 48 maxSteps: 256"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.runtime.dimensions",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "maxOutputDimension: 1024px"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "maxOutputDimension: 1024px"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.runtime.config",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Runtime"
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "panel",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Runtime"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.queue.depth",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "strong",
|
||||
"text": "0 active or waiting / 32 capacity"
|
||||
},
|
||||
"role": "strong",
|
||||
"text": "0 active or waiting / 32 capacity"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.queue.waiting",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "waitingCount: 0"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "waitingCount: 0"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.queue.active",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "hasActiveJob: False"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "hasActiveJob: False"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.runtime.queue",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Queue"
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "panel",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Queue"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.auth.mode",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "mode: off"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "mode: off"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.auth.required",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "mono",
|
||||
"text": "required: False"
|
||||
},
|
||||
"role": "mono",
|
||||
"text": "required: False"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": "repixelizer.auth.providers",
|
||||
"kind": "text",
|
||||
"props": {
|
||||
"role": "body",
|
||||
"text": "providers: none"
|
||||
},
|
||||
"role": "body",
|
||||
"text": "providers: none"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.runtime.auth",
|
||||
"kind": "card",
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Access"
|
||||
},
|
||||
"role": "group",
|
||||
"style": {
|
||||
"tone": "panel",
|
||||
"variant": "repixelizer-retro-card"
|
||||
},
|
||||
"text": "Access"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.runtime",
|
||||
"kind": "pane",
|
||||
"layout": {
|
||||
"direction": "vertical",
|
||||
"padding": 10
|
||||
},
|
||||
"props": {
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"title": "Live projection"
|
||||
},
|
||||
"role": "section",
|
||||
"style": {
|
||||
"tone": "warm",
|
||||
"variant": "repixelizer-retro-pane"
|
||||
},
|
||||
"text": "Live projection"
|
||||
}
|
||||
],
|
||||
"id": "repixelizer.surface.root",
|
||||
"kind": "surface",
|
||||
"layout": {
|
||||
"direction": "vertical",
|
||||
"padding": 14
|
||||
},
|
||||
"props": {
|
||||
"browserLowering": "https://repixelizer.gamecult.org/app/",
|
||||
"frontPage": "https://repixelizer.gamecult.org/",
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"subtitle": "Fake pixel art goes in. Real grid-aligned sprite art comes out.",
|
||||
"title": "Repixelizer"
|
||||
},
|
||||
"role": "root",
|
||||
"style": {
|
||||
"tone": "warm",
|
||||
"variant": "repixelizer-retro-shell"
|
||||
},
|
||||
"text": "Repixelizer"
|
||||
},
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"styles": {
|
||||
"assets": {
|
||||
"appUrl": "https://repixelizer.gamecult.org/app/",
|
||||
"fontCss": "https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap",
|
||||
"landingInput": "https://repixelizer.gamecult.org/app/landing-assets/character-input.png",
|
||||
"landingOutput": "https://repixelizer.gamecult.org/app/landing-assets/character-repixelized.png"
|
||||
},
|
||||
"loweringHints": {
|
||||
"android-native": "Load packaged or downloadable Press Start 2P and VT323 fonts, map style tokens before hard-coded tones, and render asset images instead of asset labels.",
|
||||
"css": "Use the tokens as CSS custom properties and retain scanline/pixel-grid overlays.",
|
||||
"tui": "Preserve title/body font distinction as role metadata; canvas editing lowers to artifact refs and command intents only."
|
||||
},
|
||||
"profile": "repixelizer.retro.pixel",
|
||||
"tokens": {
|
||||
"borderBottomWidthPx": 5,
|
||||
"borderWidthPx": 4,
|
||||
"buttonGlyph": ">",
|
||||
"colorAccent": "#ffd84a",
|
||||
"colorAccentStrong": "#ffae1a",
|
||||
"colorBackgroundBottom": "#01050d",
|
||||
"colorBackgroundMid": "#020813",
|
||||
"colorBackgroundTop": "#01040b",
|
||||
"colorCornerAccent": "#ffb72a",
|
||||
"colorMuted": "#d3bb7f",
|
||||
"colorPanel": "#0a2239",
|
||||
"colorPanelAlt": "#061627",
|
||||
"colorPanelBorder": "#0a2740",
|
||||
"colorPanelBorderDeep": "#06192a",
|
||||
"colorPixelGrid": "rgba(0, 0, 0, 0.3)",
|
||||
"colorShadow": "rgba(0, 0, 0, 0.55)",
|
||||
"colorShellSurfaceBottom": "#030b16",
|
||||
"colorShellSurfaceTop": "#061424",
|
||||
"colorText": "#efe5c9",
|
||||
"colorTextBright": "#f7efd8",
|
||||
"cornerAccentPx": 12,
|
||||
"dashedInnerFrame": true,
|
||||
"fontBody": "\"VT323\", \"Courier New\", monospace",
|
||||
"fontMono": "\"VT323\", \"Courier New\", monospace",
|
||||
"fontTitle": "\"Press Start 2P\", \"VT323\", monospace",
|
||||
"imageRendering": "pixelated",
|
||||
"scanlineOverlay": true
|
||||
}
|
||||
},
|
||||
"title": "Repixelizer"
|
||||
},
|
||||
"title": "Repixelizer",
|
||||
"type": "surface-state",
|
||||
"updatedAt": "2026-06-04T00:00:00Z",
|
||||
"version": 1
|
||||
}
|
||||
198
web/fixtures/repixelizer.provider-advertisement.json
Normal file
198
web/fixtures/repixelizer.provider-advertisement.json
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
{
|
||||
"commands": [
|
||||
{
|
||||
"authority": "repixelizer-route-policy",
|
||||
"command": "repixelizer.job.submit",
|
||||
"method": "POST",
|
||||
"result": "accepted-denied-or-queued",
|
||||
"route": "/api/jobs",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "http"
|
||||
},
|
||||
{
|
||||
"authority": "repixelizer-job-owner",
|
||||
"command": "repixelizer.job.cancel_own",
|
||||
"method": "DELETE",
|
||||
"result": "accepted-denied-or-not-found",
|
||||
"route": "/api/jobs/{job_id}",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "http"
|
||||
}
|
||||
],
|
||||
"contacts": [
|
||||
{
|
||||
"kind": "repo",
|
||||
"path": "E:\\Projects\\repixelizer"
|
||||
}
|
||||
],
|
||||
"freshness": {
|
||||
"lastSeenAt": "2026-06-04T00:00:00Z",
|
||||
"maxAgeMs": 15000,
|
||||
"state": "fresh"
|
||||
},
|
||||
"kind": "service.product",
|
||||
"nestedVerses": [
|
||||
{
|
||||
"authorityBoundary": "repixelizer-local-session-or-heimdall-account",
|
||||
"carryRules": {
|
||||
"commands": "repixelizer-route-policy-only",
|
||||
"identity": "heimdall-claims-projected-when-enabled"
|
||||
},
|
||||
"kind": "session-space",
|
||||
"parentVerseId": "gamecult.local",
|
||||
"stateSchemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.auth_projection.v0"
|
||||
],
|
||||
"surfaceIds": [
|
||||
"repixelizer.web.app"
|
||||
],
|
||||
"verseId": "repixelizer.session.{sessionId}"
|
||||
},
|
||||
{
|
||||
"authorityBoundary": "read-only-first-cut",
|
||||
"kind": "hosted-operator-space",
|
||||
"parentVerseId": "gamecult.local",
|
||||
"stateSchemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.queue_snapshot.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.runtime_config.v0",
|
||||
"repixelizer.auth_projection.v0"
|
||||
],
|
||||
"surfaceIds": [
|
||||
"repixelizer.operator.surface"
|
||||
],
|
||||
"verseId": "repixelizer.operator"
|
||||
}
|
||||
],
|
||||
"providerId": "repixelizer",
|
||||
"runtime": {
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"mode": "off",
|
||||
"protectQueue": false,
|
||||
"providers": [],
|
||||
"required": false
|
||||
},
|
||||
"hostedDemo": false,
|
||||
"queueCapacity": 32,
|
||||
"showQueuePanel": false,
|
||||
"spoolPath": "C:\\Users\\Meta\\AppData\\Local\\Temp\\repixelizer-gui-spool"
|
||||
},
|
||||
"schema": "gamecult.eve.provider_advertisement.v1",
|
||||
"schemas": [
|
||||
{
|
||||
"authority": "accepted",
|
||||
"description": "Job id, owner binding, status, timestamps, input/output artifact refs, error summary, and retention state.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.job.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "accepted",
|
||||
"description": "Queue capacity, waiting/running counts, active job ids, oldest waiting age, and hosted mode.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.queue_snapshot.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "redacted-projection",
|
||||
"description": "Redacted progress event, stage, index, timestamp, and job id.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.job_event.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "accepted",
|
||||
"description": "Hosted flags, queue limits, visible UI flags, solver config hash placeholder, deployment id, and spool path.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.runtime_config.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "external-authority-projection",
|
||||
"description": "Auth mode, provider availability, subject capability summary, and Heimdall claim freshness without raw tokens.",
|
||||
"owner": "repixelizer.access",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.auth_projection.v0",
|
||||
"storage": "heimdall-projection-with-cc-witness-reserved"
|
||||
}
|
||||
],
|
||||
"serviceId": "repixelizer.service",
|
||||
"styleCapabilities": [
|
||||
{
|
||||
"lossiness": {
|
||||
"read-only-first-cut": "operator surface names state and routes but does not own queue truth",
|
||||
"tui": "no-pixel-canvas-editing; status, queue, artifact refs, and command intents only"
|
||||
},
|
||||
"preferredLowerings": [
|
||||
"css",
|
||||
"android-native",
|
||||
"native-eve",
|
||||
"tui"
|
||||
],
|
||||
"styleProfile": "repixelizer.product",
|
||||
"tokenGroups": [
|
||||
"repixelizer.retro.pixel",
|
||||
"repixelizer.comparisonCanvas",
|
||||
"repixelizer.cleanupTool",
|
||||
"repixelizer.queuePanel",
|
||||
"repixelizer.operatorStatus"
|
||||
]
|
||||
}
|
||||
],
|
||||
"surfaces": [
|
||||
{
|
||||
"audience": "operator",
|
||||
"commands": [
|
||||
"repixelizer.web.open",
|
||||
"repixelizer.job.submit",
|
||||
"repixelizer.job.cancel_own"
|
||||
],
|
||||
"key": "cultmesh://repixelizer/surfaces/operator",
|
||||
"mode": "read-only-first-cut",
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"status": "available",
|
||||
"styleProfile": "repixelizer.retro.pixel",
|
||||
"surfaceId": "repixelizer.operator.surface",
|
||||
"transport": "http-json",
|
||||
"url": "https://repixelizer.gamecult.org/eve/surface"
|
||||
},
|
||||
{
|
||||
"canonical": false,
|
||||
"canonicalSurfaceId": "repixelizer.operator.surface",
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "browser-lowering",
|
||||
"url": "https://repixelizer.gamecult.org/app/"
|
||||
}
|
||||
],
|
||||
"title": "Repixelizer",
|
||||
"updatedAt": "2026-06-04T00:00:00Z",
|
||||
"verseId": "gamecult.local",
|
||||
"witnesses": [
|
||||
{
|
||||
"freshness": {
|
||||
"state": "planned",
|
||||
"updatedAt": "2026-06-04T00:00:00Z"
|
||||
},
|
||||
"id": "repixelizer.hosted.witness",
|
||||
"kind": "cc-export-path-reserved",
|
||||
"path": "state/repixelizer.witness.cc",
|
||||
"redaction": "bulk-images-raw-tokens-and-private-claims-removed",
|
||||
"schemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.queue_snapshot.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.runtime_config.v0",
|
||||
"repixelizer.auth_projection.v0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -19,13 +19,11 @@
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="tabs">
|
||||
<button id="voidbot-tab" class="active" type="button">VoidBot Live</button>
|
||||
<button id="fensalir-tab" type="button">Fensalir Direct2D</button>
|
||||
<button id="sai-tab" type="button">Sai VN Surface</button>
|
||||
<button id="huginn-tab" type="button">Huginn .cc</button>
|
||||
<button id="dsl-tab" type="button">Reactive DSL</button>
|
||||
</nav>
|
||||
<section class="provider-bar" aria-label="Provider target">
|
||||
<label for="provider-select">Target</label>
|
||||
<select id="provider-select"></select>
|
||||
<div id="provider-meta" class="provider-meta">loading providers</div>
|
||||
</section>
|
||||
|
||||
<main id="app" class="surface"></main>
|
||||
|
||||
|
|
|
|||
111
web/styles.css
111
web/styles.css
|
|
@ -1,14 +1,29 @@
|
|||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #020909;
|
||||
--bg-top: var(--bg);
|
||||
--bg-mid: var(--bg);
|
||||
--bg-bottom: var(--bg);
|
||||
--shell-top: #010707;
|
||||
--shell-bottom: #010707;
|
||||
--panel: #071918;
|
||||
--panel-2: #0a2423;
|
||||
--line: rgba(103, 240, 228, 0.28);
|
||||
--line-deep: rgba(103, 240, 228, 0.18);
|
||||
--line-hot: rgba(142, 252, 255, 0.72);
|
||||
--text: #e7f1f1;
|
||||
--text-bright: var(--text);
|
||||
--quiet: #8ba5a3;
|
||||
--accent: #ffb84f;
|
||||
--accent-strong: var(--accent);
|
||||
--corner-accent: var(--accent);
|
||||
--cyan: #8efcff;
|
||||
--shadow: rgba(0, 0, 0, 0.35);
|
||||
--pixel-grid: rgba(142, 252, 255, 0.04);
|
||||
--border-width: 1px;
|
||||
--border-bottom-width: var(--border-width);
|
||||
--corner-accent-size: 0;
|
||||
--font-mono: "Cascadia Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -18,7 +33,7 @@
|
|||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
background: linear-gradient(180deg, var(--bg-top), var(--bg-mid), var(--bg-bottom));
|
||||
color: var(--text);
|
||||
font-family: var(--font-body, Inter), "Segoe UI", system-ui, sans-serif;
|
||||
}
|
||||
|
|
@ -30,7 +45,7 @@ body {
|
|||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: #010707;
|
||||
background: linear-gradient(180deg, var(--shell-top), var(--shell-bottom));
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
@ -58,25 +73,37 @@ p {
|
|||
gap: 2px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
.provider-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px 0;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid var(--line-deep);
|
||||
background: color-mix(in srgb, var(--panel) 70%, transparent);
|
||||
}
|
||||
|
||||
.tabs button {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px 6px 0 0;
|
||||
background: var(--panel);
|
||||
.provider-bar label {
|
||||
color: var(--quiet);
|
||||
padding: 8px 12px;
|
||||
font: 600 13px "Cascadia Mono", Consolas, monospace;
|
||||
font: 700 11px var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tabs button.active {
|
||||
color: var(--cyan);
|
||||
border-color: var(--line-hot);
|
||||
background: var(--panel-2);
|
||||
.provider-bar select {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
padding: 8px 12px;
|
||||
font: 700 13px var(--font-mono);
|
||||
min-width: min(28rem, 52vw);
|
||||
}
|
||||
|
||||
.provider-meta {
|
||||
color: var(--quiet);
|
||||
font: 600 11px var(--font-mono);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.surface {
|
||||
|
|
@ -105,6 +132,7 @@ p {
|
|||
border-radius: 6px;
|
||||
background: rgba(7, 25, 24, 0.92);
|
||||
padding: 12px;
|
||||
box-shadow: 0 12px 26px var(--shadow);
|
||||
}
|
||||
|
||||
.pane h2 {
|
||||
|
|
@ -120,6 +148,12 @@ p {
|
|||
border-radius: 6px;
|
||||
background: rgba(10, 36, 35, 0.96);
|
||||
padding: 10px;
|
||||
box-shadow: 0 8px 20px var(--shadow);
|
||||
}
|
||||
|
||||
.cultui-card[data-command-id] {
|
||||
cursor: pointer;
|
||||
border-bottom-width: var(--border-bottom-width);
|
||||
}
|
||||
|
||||
.ctb .card {
|
||||
|
|
@ -140,17 +174,17 @@ p {
|
|||
.card-title {
|
||||
white-space: pre-line;
|
||||
margin-top: 4px;
|
||||
font: 700 11px "Cascadia Mono", Consolas, monospace;
|
||||
font: 700 11px var(--font-mono);
|
||||
}
|
||||
|
||||
.health {
|
||||
color: var(--accent);
|
||||
font: 700 10px "Cascadia Mono", Consolas, monospace;
|
||||
font: 700 10px var(--font-mono);
|
||||
}
|
||||
|
||||
.detail {
|
||||
white-space: pre-wrap;
|
||||
font: 600 12px "Cascadia Mono", Consolas, monospace;
|
||||
font: 600 12px var(--font-mono);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +203,7 @@ p {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--quiet);
|
||||
font: 700 10px "Cascadia Mono", Consolas, monospace;
|
||||
font: 700 10px var(--font-mono);
|
||||
}
|
||||
|
||||
.bar {
|
||||
|
|
@ -192,8 +226,8 @@ p {
|
|||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background:
|
||||
linear-gradient(rgba(142, 252, 255, 0.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(142, 252, 255, 0.04) 1px, transparent 1px),
|
||||
linear-gradient(var(--pixel-grid) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--pixel-grid) 1px, transparent 1px),
|
||||
rgba(3, 15, 17, 0.94);
|
||||
background-size: 44px 44px;
|
||||
}
|
||||
|
|
@ -242,12 +276,49 @@ p {
|
|||
align-content: start;
|
||||
}
|
||||
|
||||
.cultui-surface-root {
|
||||
min-height: calc(100vh - 150px);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.cultui-pane {
|
||||
border-width: var(--border-width);
|
||||
border-bottom-width: var(--border-bottom-width);
|
||||
}
|
||||
|
||||
body[data-provider="repixelizer"] .surface {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body[data-provider="repixelizer"] .surface::after {
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.035) 0 1px, transparent 1px 4px);
|
||||
opacity: 0.28;
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
body[data-pixel-art="true"] img,
|
||||
body[data-pixel-art="true"] .cultui-background {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.cultui-title {
|
||||
color: var(--accent);
|
||||
font: 700 16px "Cascadia Mono", Consolas, monospace;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.mono {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.cultui-reactive-metric {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
|
|
|||
256
web/surface.js
256
web/surface.js
|
|
@ -4,57 +4,147 @@ const statusEl = document.querySelector("#status");
|
|||
const app = document.querySelector("#app");
|
||||
const surfaceId = document.querySelector("#surface-id");
|
||||
const surfaceVersion = document.querySelector("#surface-version");
|
||||
const voidBotTab = document.querySelector("#voidbot-tab");
|
||||
const fensalirTab = document.querySelector("#fensalir-tab");
|
||||
const saiTab = document.querySelector("#sai-tab");
|
||||
const huginnTab = document.querySelector("#huginn-tab");
|
||||
const dslTab = document.querySelector("#dsl-tab");
|
||||
const providerSelect = document.querySelector("#provider-select");
|
||||
const providerMeta = document.querySelector("#provider-meta");
|
||||
|
||||
let socket;
|
||||
let providers = [];
|
||||
let currentProvider;
|
||||
|
||||
voidBotTab.addEventListener("click", () => {
|
||||
setActiveTab(voidBotTab);
|
||||
openVoidBot();
|
||||
const defaultStyleTokens = {
|
||||
"--bg": "#020909",
|
||||
"--bg-top": "#020909",
|
||||
"--bg-mid": "#020909",
|
||||
"--bg-bottom": "#020909",
|
||||
"--shell-top": "#010707",
|
||||
"--shell-bottom": "#010707",
|
||||
"--panel": "#071918",
|
||||
"--panel-2": "#0a2423",
|
||||
"--line": "rgba(103, 240, 228, 0.28)",
|
||||
"--line-deep": "rgba(103, 240, 228, 0.18)",
|
||||
"--text": "#e7f1f1",
|
||||
"--text-bright": "#e7f1f1",
|
||||
"--quiet": "#8ba5a3",
|
||||
"--accent": "#ffb84f",
|
||||
"--accent-strong": "#ffb84f",
|
||||
"--corner-accent": "#ffb84f",
|
||||
"--cyan": "#8efcff",
|
||||
"--shadow": "rgba(0, 0, 0, 0.35)",
|
||||
"--pixel-grid": "rgba(142, 252, 255, 0.04)",
|
||||
"--border-width": "1px",
|
||||
"--border-bottom-width": "1px",
|
||||
"--corner-accent-size": "0",
|
||||
"--font-body": "Inter, Segoe UI, system-ui, sans-serif",
|
||||
"--font-title": "Cascadia Mono, Consolas, monospace",
|
||||
"--font-mono": "Cascadia Mono, Consolas, monospace",
|
||||
};
|
||||
|
||||
const localProviders = [
|
||||
{
|
||||
providerId: "voidbot.swarm",
|
||||
title: "VoidBot Live",
|
||||
kind: "service.operator",
|
||||
freshness: { state: "live" },
|
||||
surfaces: [{ transport: "mimir-eve-deck", surfaceId: "voidbot.swarm" }],
|
||||
},
|
||||
{
|
||||
providerId: "repixelizer",
|
||||
title: "Repixelizer",
|
||||
kind: "service.product",
|
||||
advertisement: "./fixtures/repixelizer.provider-advertisement.json",
|
||||
surfaces: [{ transport: "local-json", surfaceId: "repixelizer.operator.surface", url: "./fixtures/repixelizer.eve-surface.json" }],
|
||||
},
|
||||
{
|
||||
providerId: "fensalir.direct2d",
|
||||
title: "Fensalir Direct2D",
|
||||
kind: "surface.renderer",
|
||||
freshness: { state: "fixture" },
|
||||
surfaces: [{ transport: "local-json", surfaceId: "fensalir.direct2d.fixture", url: "./fixtures/fensalir-client-surface.json" }],
|
||||
},
|
||||
{
|
||||
providerId: "sai.visual_novel",
|
||||
title: "Sai VN Surface",
|
||||
kind: "content.runtime",
|
||||
freshness: { state: "fixture" },
|
||||
surfaces: [{ transport: "local-json", surfaceId: "sai.visual_novel.surface", url: "./fixtures/sai-vn-surface.json" }],
|
||||
},
|
||||
{
|
||||
providerId: "cultcache.huginn.inspector",
|
||||
title: "Huginn .cc",
|
||||
kind: "inspection.huginn",
|
||||
freshness: { state: "fixture" },
|
||||
surfaces: [{ transport: "local-eve-dsl", surfaceId: "cultcache.huginn.inspector", url: "./fixtures/huginn-cc-surface.eve" }],
|
||||
},
|
||||
{
|
||||
providerId: "eve.reactive.dsl",
|
||||
title: "Reactive DSL",
|
||||
kind: "surface.fixture",
|
||||
freshness: { state: "fixture" },
|
||||
surfaces: [{ transport: "local-eve-dsl", surfaceId: "eve.reactive.dsl", url: "./fixtures/reactive-composition.eve" }],
|
||||
},
|
||||
];
|
||||
|
||||
providerSelect.addEventListener("change", () => {
|
||||
const provider = providers.find(candidate => candidate.providerId === providerSelect.value);
|
||||
if (provider) void openProvider(provider);
|
||||
});
|
||||
|
||||
fensalirTab.addEventListener("click", async () => {
|
||||
setActiveTab(fensalirTab);
|
||||
closeSocket();
|
||||
const response = await fetch("./fixtures/fensalir-client-surface.json");
|
||||
renderSurface(await response.json(), "fixture");
|
||||
});
|
||||
void bootProviders();
|
||||
|
||||
saiTab.addEventListener("click", async () => {
|
||||
setActiveTab(saiTab);
|
||||
closeSocket();
|
||||
const response = await fetch("./fixtures/sai-vn-surface.json");
|
||||
renderSurface(await response.json(), "fixture");
|
||||
});
|
||||
|
||||
huginnTab.addEventListener("click", async () => {
|
||||
setActiveTab(huginnTab);
|
||||
closeSocket();
|
||||
const response = await fetch("./fixtures/huginn-cc-surface.eve");
|
||||
renderSurface(compileEveDsl(await response.text()), "dsl");
|
||||
});
|
||||
|
||||
dslTab.addEventListener("click", async () => {
|
||||
setActiveTab(dslTab);
|
||||
closeSocket();
|
||||
const response = await fetch("./fixtures/reactive-composition.eve");
|
||||
renderSurface(compileEveDsl(await response.text()), "dsl");
|
||||
});
|
||||
|
||||
openVoidBot();
|
||||
|
||||
function setActiveTab(tab) {
|
||||
for (const button of [voidBotTab, fensalirTab, saiTab, huginnTab, dslTab]) {
|
||||
button.classList.toggle("active", button === tab);
|
||||
}
|
||||
async function bootProviders() {
|
||||
providers = await Promise.all(localProviders.map(loadProviderAdvertisement));
|
||||
providerSelect.replaceChildren(...providers.map(provider => {
|
||||
const option = document.createElement("option");
|
||||
option.value = provider.providerId;
|
||||
option.textContent = provider.title || provider.providerId;
|
||||
return option;
|
||||
}));
|
||||
const firstProduct = providers.find(provider => provider.providerId === "repixelizer") || providers[0];
|
||||
providerSelect.value = firstProduct.providerId;
|
||||
await openProvider(firstProduct);
|
||||
}
|
||||
|
||||
function openVoidBot() {
|
||||
async function loadProviderAdvertisement(provider) {
|
||||
if (!provider.advertisement) return provider;
|
||||
const response = await fetch(provider.advertisement);
|
||||
const advertisement = await response.json();
|
||||
return {
|
||||
...advertisement,
|
||||
...provider,
|
||||
surfaces: provider.surfaces || advertisement.surfaces || [],
|
||||
localAdvertisement: advertisement,
|
||||
};
|
||||
}
|
||||
|
||||
async function openProvider(provider) {
|
||||
closeSocket();
|
||||
currentProvider = provider;
|
||||
providerMeta.textContent = `${provider.kind || "provider"} | ${provider.providerId} | ${provider.freshness?.state || "unknown"}`;
|
||||
surfaceId.textContent = provider.providerId;
|
||||
surfaceVersion.textContent = "opening";
|
||||
const surface = provider.surfaces?.[0];
|
||||
if (!surface) {
|
||||
statusEl.textContent = `${provider.title || provider.providerId} has no advertised surface`;
|
||||
app.replaceChildren(emptyState("No advertised surface"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (surface.transport === "mimir-eve-deck") {
|
||||
openVoidBot(provider);
|
||||
return;
|
||||
}
|
||||
|
||||
if (surface.transport === "local-eve-dsl") {
|
||||
const response = await fetch(surface.url);
|
||||
renderSurface(compileEveDsl(await response.text()), "local dsl");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(surface.url);
|
||||
renderSurface(await response.json(), "local fixture");
|
||||
}
|
||||
|
||||
function openVoidBot(provider) {
|
||||
const scheme = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const host = location.hostname || "127.0.0.1";
|
||||
const url = `${scheme}//${host}:8795/eve/deck`;
|
||||
|
|
@ -64,7 +154,7 @@ function openVoidBot() {
|
|||
socket.addEventListener("open", () => {
|
||||
if (socket !== activeSocket) return;
|
||||
statusEl.textContent = "connected to Mimir Eve broker";
|
||||
activeSocket.send(JSON.stringify({ type: "open-provider", providerId: "voidbot.swarm" }));
|
||||
activeSocket.send(JSON.stringify({ type: "open-provider", providerId: provider.providerId }));
|
||||
});
|
||||
socket.addEventListener("message", event => {
|
||||
if (socket !== activeSocket) return;
|
||||
|
|
@ -96,6 +186,7 @@ function renderSurface(state, source) {
|
|||
surfaceVersion.textContent = `v${state.version ?? "?"}`;
|
||||
statusEl.textContent = `${state.title || "surface"} (${source})`;
|
||||
applySurfaceStyles(state.surface?.styles);
|
||||
document.body.dataset.provider = state.providerId || currentProvider?.providerId || "";
|
||||
if (state.surface?.root) {
|
||||
app.replaceChildren(renderCultComponent(state.surface.root));
|
||||
} else if (state.providerId === "voidbot.swarm") {
|
||||
|
|
@ -108,20 +199,41 @@ function renderSurface(state, source) {
|
|||
function applySurfaceStyles(styles) {
|
||||
const tokens = styles?.tokens || {};
|
||||
const root = document.documentElement.style;
|
||||
for (const [variable, value] of Object.entries(defaultStyleTokens)) {
|
||||
root.setProperty(variable, value);
|
||||
}
|
||||
const map = {
|
||||
colorBackground: "--bg",
|
||||
colorBackgroundTop: "--bg-top",
|
||||
colorBackgroundMid: "--bg-mid",
|
||||
colorBackgroundBottom: "--bg-bottom",
|
||||
colorShellSurfaceTop: "--shell-top",
|
||||
colorShellSurfaceBottom: "--shell-bottom",
|
||||
colorPanel: "--panel",
|
||||
colorPanelAlt: "--panel-2",
|
||||
colorPanelBorder: "--line",
|
||||
colorPanelBorderDeep: "--line-deep",
|
||||
colorText: "--text",
|
||||
colorTextBright: "--text-bright",
|
||||
colorMuted: "--quiet",
|
||||
colorAccent: "--accent",
|
||||
colorAccentStrong: "--accent-strong",
|
||||
colorCornerAccent: "--corner-accent",
|
||||
colorLink: "--cyan",
|
||||
colorShadow: "--shadow",
|
||||
colorPixelGrid: "--pixel-grid",
|
||||
};
|
||||
for (const [token, variable] of Object.entries(map)) {
|
||||
if (tokens[token]) root.setProperty(variable, tokens[token]);
|
||||
}
|
||||
if (tokens.fontBody) root.setProperty("--font-body", tokens.fontBody);
|
||||
if (tokens.fontTitle) root.setProperty("--font-title", tokens.fontTitle);
|
||||
if (tokens.fontMono) root.setProperty("--font-mono", tokens.fontMono);
|
||||
if (tokens.borderWidthPx) root.setProperty("--border-width", `${tokens.borderWidthPx}px`);
|
||||
if (tokens.borderBottomWidthPx) root.setProperty("--border-bottom-width", `${tokens.borderBottomWidthPx}px`);
|
||||
if (tokens.cornerAccentPx) root.setProperty("--corner-accent-size", `${tokens.cornerAccentPx}px`);
|
||||
document.body.dataset.pixelArt = tokens.pixelArt || tokens.imageRendering === "pixelated" ? "true" : "false";
|
||||
document.body.dataset.scanlines = tokens.scanlineOverlay ? "true" : "false";
|
||||
}
|
||||
|
||||
function renderCultComponent(node) {
|
||||
|
|
@ -187,10 +299,34 @@ function renderCultComponent(node) {
|
|||
return card;
|
||||
}
|
||||
|
||||
if (kind === "surface") {
|
||||
const surface = el("section", "cultui-surface-root");
|
||||
for (const child of children) surface.append(renderCultComponent(child));
|
||||
return surface;
|
||||
}
|
||||
|
||||
if (kind === "pane" || kind === "panel") {
|
||||
const pane = el("section", "pane cultui-pane");
|
||||
if (props.title || node.text) pane.append(el("h2", "", props.title || node.text));
|
||||
for (const child of children) pane.append(renderCultComponent(child));
|
||||
return pane;
|
||||
}
|
||||
|
||||
if (kind === "card") {
|
||||
const card = el("article", "card cultui-card");
|
||||
if (props.title) card.append(el("div", "card-title", props.title));
|
||||
if (props.title || node.text) card.append(el("div", "card-title", props.title || node.text));
|
||||
for (const child of children) card.append(renderCultComponent(child));
|
||||
if (props.commandId || node.commandId) {
|
||||
card.dataset.commandId = props.commandId || node.commandId;
|
||||
card.tabIndex = 0;
|
||||
card.addEventListener("click", () => publishCommandIntent(card.dataset.commandId, props));
|
||||
card.addEventListener("keydown", event => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
publishCommandIntent(card.dataset.commandId, props);
|
||||
}
|
||||
});
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +348,7 @@ function renderCultComponent(node) {
|
|||
}
|
||||
|
||||
if (kind === "text.dialogue" || kind === "text" || kind === "text.title") {
|
||||
const text = el("div", kind === "text.title" ? "cultui-title" : "detail", props.text || "");
|
||||
const text = el("div", textClassName(kind, props), props.text || node.text || "");
|
||||
bindText(text, props);
|
||||
return text;
|
||||
}
|
||||
|
|
@ -327,6 +463,36 @@ function currentMesh() {
|
|||
return window.__eveCurrentMesh;
|
||||
}
|
||||
|
||||
function textClassName(kind, props) {
|
||||
if (kind === "text.title" || props.role === "title") return "cultui-title";
|
||||
if (props.role === "mono") return "detail mono";
|
||||
return "detail";
|
||||
}
|
||||
|
||||
function emptyState(message) {
|
||||
const pane = el("section", "pane");
|
||||
pane.append(el("h2", "", "No Surface"));
|
||||
pane.append(el("div", "detail", message));
|
||||
return pane;
|
||||
}
|
||||
|
||||
function publishCommandIntent(commandId, props = {}) {
|
||||
const command = {
|
||||
type: "surface-command",
|
||||
schema: "gamecult.eve.command.v1",
|
||||
providerId: currentProvider?.providerId || surfaceId.textContent,
|
||||
surfaceId: currentProvider?.surfaces?.[0]?.surfaceId || surfaceId.textContent,
|
||||
command: commandId || "invoke",
|
||||
payload: {
|
||||
transport: props.transport || null,
|
||||
},
|
||||
issuedAt: new Date().toISOString(),
|
||||
clientId: "browser.reference",
|
||||
};
|
||||
statusEl.textContent = `command ${command.command}`;
|
||||
console.info("Eve command intent", command);
|
||||
}
|
||||
|
||||
function renderCultGraph(props) {
|
||||
const graph = el("section", "cultui-graph");
|
||||
graph.dataset.engine = props.engine?.id || "graph";
|
||||
|
|
|
|||
Loading…
Reference in a new issue