@legionworks/facet 1.9.0
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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +23 -0
- package/README.md +158 -0
- package/dist/gallery/chunk-4g8rem85.css +656 -0
- package/dist/gallery/chunk-c6s9k9ty.js +5970 -0
- package/dist/gallery/frame/artifact.css +8424 -0
- package/dist/gallery/frame/chunks/abnfDiagram-N423BO3Z-jj74szbm.js +155 -0
- package/dist/gallery/frame/chunks/architecture-TIHT7OUA-cvdjn7dt.js +10 -0
- package/dist/gallery/frame/chunks/architectureDiagram-T3A2C74G-n4z77eg6.js +8675 -0
- package/dist/gallery/frame/chunks/blockDiagram-VBNYF7ZC-7xsqzrd1.js +3529 -0
- package/dist/gallery/frame/chunks/c4Diagram-5PPSVZJV-z2zjs7wx.js +2413 -0
- package/dist/gallery/frame/chunks/classDiagram-JCYQIIEL-ve8rngqp.js +47 -0
- package/dist/gallery/frame/chunks/classDiagram-v2-OCEON4UE-8cfaxb8z.js +47 -0
- package/dist/gallery/frame/chunks/cose-bilkent-JH36ORCC-sn92qexx.js +4822 -0
- package/dist/gallery/frame/chunks/cynefin-VYW2F7L2-tqjv913d.js +10 -0
- package/dist/gallery/frame/chunks/cynefinDiagram-MW4NZA55-c1c0dj12.js +500 -0
- package/dist/gallery/frame/chunks/dagre-VZM6K2ZE-7tyg4yt2.js +476 -0
- package/dist/gallery/frame/chunks/diagram-7IWD3JNH-y4eng9sc.js +526 -0
- package/dist/gallery/frame/chunks/diagram-B4RE2ZJO-rn3p7j5h.js +673 -0
- package/dist/gallery/frame/chunks/diagram-LBJQPF4R-w4a2cm9q.js +255 -0
- package/dist/gallery/frame/chunks/diagram-Q27KOJAE-aqc2v7wy.js +575 -0
- package/dist/gallery/frame/chunks/diagram-UB23O5K3-xncy8nyd.js +355 -0
- package/dist/gallery/frame/chunks/ebnfDiagram-BXEA7PRR-acwgxz16.js +175 -0
- package/dist/gallery/frame/chunks/erDiagram-JOGREHBK-mzp5nswt.js +1332 -0
- package/dist/gallery/frame/chunks/eventmodeling-45OFAUF4-sg07qr0c.js +10 -0
- package/dist/gallery/frame/chunks/flowDiagram-UKHOOZJN-2e5ygs44.js +29 -0
- package/dist/gallery/frame/chunks/ganttDiagram-PKOTCBZU-p2hhh7hr.js +2620 -0
- package/dist/gallery/frame/chunks/gitGraph-TEB2WS4Q-j6kw0naa.js +10 -0
- package/dist/gallery/frame/chunks/gitGraphDiagram-DS77QQ5N-4z9z8wjm.js +1347 -0
- package/dist/gallery/frame/chunks/info-DKCQHKI2-mpt80jd3.js +10 -0
- package/dist/gallery/frame/chunks/infoDiagram-6WML65LV-5rqm3mgg.js +65 -0
- package/dist/gallery/frame/chunks/ishikawaDiagram-WSZJBQD7-b51z96jy.js +975 -0
- package/dist/gallery/frame/chunks/journeyDiagram-NVQOT4AX-9jbfp0pk.js +1272 -0
- package/dist/gallery/frame/chunks/kanban-definition-27J2QSJJ-4x0vx4se.js +1116 -0
- package/dist/gallery/frame/chunks/katex-xfgk95tv.js +14034 -0
- package/dist/gallery/frame/chunks/markdown-1s5vwkf2.js +9 -0
- package/dist/gallery/frame/chunks/markdown-2h6bwsms.js +3239 -0
- package/dist/gallery/frame/chunks/markdown-2x113yh8.js +107 -0
- package/dist/gallery/frame/chunks/markdown-30b7rwmp.js +1943 -0
- package/dist/gallery/frame/chunks/markdown-356bfj4g.js +2031 -0
- package/dist/gallery/frame/chunks/markdown-44n9ea69.js +2406 -0
- package/dist/gallery/frame/chunks/markdown-56enrf2r.js +39 -0
- package/dist/gallery/frame/chunks/markdown-5avymm8t.js +725 -0
- package/dist/gallery/frame/chunks/markdown-6r4svars.js +33 -0
- package/dist/gallery/frame/chunks/markdown-6zz8efx2.js +24 -0
- package/dist/gallery/frame/chunks/markdown-7jp6prnb.js +19 -0
- package/dist/gallery/frame/chunks/markdown-809k7q41.js +55 -0
- package/dist/gallery/frame/chunks/markdown-82xmzhzd.js +1076 -0
- package/dist/gallery/frame/chunks/markdown-92n37gd4.js +7627 -0
- package/dist/gallery/frame/chunks/markdown-ag2sssf8.js +36 -0
- package/dist/gallery/frame/chunks/markdown-b4s7ntq2.js +19 -0
- package/dist/gallery/frame/chunks/markdown-c9k0fvp1.js +100 -0
- package/dist/gallery/frame/chunks/markdown-ceefymn8.js +125 -0
- package/dist/gallery/frame/chunks/markdown-d1wfbs70.js +243 -0
- package/dist/gallery/frame/chunks/markdown-dy5sgjhc.js +11022 -0
- package/dist/gallery/frame/chunks/markdown-dz3z5w30.js +30868 -0
- package/dist/gallery/frame/chunks/markdown-eg6ngz7y.js +30340 -0
- package/dist/gallery/frame/chunks/markdown-ff5wvb43.js +1149 -0
- package/dist/gallery/frame/chunks/markdown-ffg06nfz.js +86 -0
- package/dist/gallery/frame/chunks/markdown-fvfncadf.js +472 -0
- package/dist/gallery/frame/chunks/markdown-gvyszqpv.js +19 -0
- package/dist/gallery/frame/chunks/markdown-gx1p0ew0.js +88 -0
- package/dist/gallery/frame/chunks/markdown-gxye5frz.js +65 -0
- package/dist/gallery/frame/chunks/markdown-h7pzv34e.js +105 -0
- package/dist/gallery/frame/chunks/markdown-hab789t9.js +47 -0
- package/dist/gallery/frame/chunks/markdown-hjp6sj8t.js +125 -0
- package/dist/gallery/frame/chunks/markdown-jka64qgp.js +1993 -0
- package/dist/gallery/frame/chunks/markdown-k8xkn8mw.js +56 -0
- package/dist/gallery/frame/chunks/markdown-m7frq8x8.js +112 -0
- package/dist/gallery/frame/chunks/markdown-p740v4qm.js +36 -0
- package/dist/gallery/frame/chunks/markdown-pxfeh231.js +57 -0
- package/dist/gallery/frame/chunks/markdown-pzgd8z3s.js +87 -0
- package/dist/gallery/frame/chunks/markdown-qx3cngz2.js +1673 -0
- package/dist/gallery/frame/chunks/markdown-rawhfvvc.js +85 -0
- package/dist/gallery/frame/chunks/markdown-rd8w0ng9.js +49 -0
- package/dist/gallery/frame/chunks/markdown-rm2yxfjj.js +2773 -0
- package/dist/gallery/frame/chunks/markdown-s1jatrnk.js +993 -0
- package/dist/gallery/frame/chunks/markdown-sgfg5vvq.js +59 -0
- package/dist/gallery/frame/chunks/markdown-t4ethv9d.js +409 -0
- package/dist/gallery/frame/chunks/markdown-tnmabqvz.js +36 -0
- package/dist/gallery/frame/chunks/markdown-tvdpppw8.js +5292 -0
- package/dist/gallery/frame/chunks/markdown-v0x27twr.js +22 -0
- package/dist/gallery/frame/chunks/markdown-v14td68x.js +36 -0
- package/dist/gallery/frame/chunks/markdown-w6sgd54z.js +36 -0
- package/dist/gallery/frame/chunks/markdown-xw8dsk8b.js +2125 -0
- package/dist/gallery/frame/chunks/markdown-y18mve73.js +85 -0
- package/dist/gallery/frame/chunks/markdown-yengsms5.js +427 -0
- package/dist/gallery/frame/chunks/markdown-z1wc0v0d.js +1826 -0
- package/dist/gallery/frame/chunks/markdown-zazx9h4n.js +329 -0
- package/dist/gallery/frame/chunks/mermaid-de7p4v0x.js +30 -0
- package/dist/gallery/frame/chunks/mindmap-definition-FAOFIHXS-ra44cbem.js +1216 -0
- package/dist/gallery/frame/chunks/packet-7NZHBO7P-xppm5913.js +10 -0
- package/dist/gallery/frame/chunks/pegDiagram-VL7TDLO6-a2a4wg03.js +163 -0
- package/dist/gallery/frame/chunks/pie-RZYD4A2V-hnbxk152.js +10 -0
- package/dist/gallery/frame/chunks/pieDiagram-7S7Q4E2Y-f1vxjtmx.js +311 -0
- package/dist/gallery/frame/chunks/quadrantDiagram-CIZ2JOQS-ywafa6kh.js +1390 -0
- package/dist/gallery/frame/chunks/radar-I7S5WNFK-ewqd27se.js +10 -0
- package/dist/gallery/frame/chunks/railroad-3IZDKUUU-5hy4bdx3.js +10 -0
- package/dist/gallery/frame/chunks/railroad-abnf-AHOZXSZD-bh5sxz0d.js +10 -0
- package/dist/gallery/frame/chunks/railroad-ebnf-EBAXGLYW-6b9c3zeh.js +10 -0
- package/dist/gallery/frame/chunks/railroad-peg-LSFZ7HO6-avc8v39s.js +10 -0
- package/dist/gallery/frame/chunks/railroadDiagram-AXF67PYL-qc61wax4.js +131 -0
- package/dist/gallery/frame/chunks/requirementDiagram-LRYGKXZP-9bs4786c.js +1295 -0
- package/dist/gallery/frame/chunks/sankeyDiagram-W5VNT64P-awt1em8b.js +1339 -0
- package/dist/gallery/frame/chunks/sequenceDiagram-SI44F4Z6-qaf9mz3t.js +4324 -0
- package/dist/gallery/frame/chunks/sizeCapture-X5ZJPWSS-9fq685xd.js +64 -0
- package/dist/gallery/frame/chunks/stateDiagram-OKZ733FA-vx7a7hh0.js +456 -0
- package/dist/gallery/frame/chunks/stateDiagram-v2-UEYNNEHI-x4m6ws5t.js +46 -0
- package/dist/gallery/frame/chunks/swimlanes-SLNWSIFB-e4mxm1gh.js +8287 -0
- package/dist/gallery/frame/chunks/swimlanesDiagram-ULZ7WXOC-0h93e2qq.js +42 -0
- package/dist/gallery/frame/chunks/timeline-definition-Z64GVDOM-wp0bhbzg.js +1549 -0
- package/dist/gallery/frame/chunks/treeView-QDETBFTQ-hcx7n0vt.js +10 -0
- package/dist/gallery/frame/chunks/treemap-6X3UGDF4-av02skdw.js +10 -0
- package/dist/gallery/frame/chunks/vennDiagram-T6HMQDX7-pmvk1p7n.js +2566 -0
- package/dist/gallery/frame/chunks/wardley-OPB4EBWU-njsp6e40.js +10 -0
- package/dist/gallery/frame/chunks/wardleyDiagram-T6FBY63Y-eggmnv4z.js +979 -0
- package/dist/gallery/frame/chunks/xychartDiagram-ELKLHX3M-q40tnpe5.js +1954 -0
- package/dist/gallery/frame/frame.css +183 -0
- package/dist/gallery/frame/runtime/chart.js +42744 -0
- package/dist/gallery/frame/runtime/html.js +14 -0
- package/dist/gallery/frame/runtime/markdown.js +1403 -0
- package/dist/gallery/frame/runtime/mermaid.js +32 -0
- package/dist/gallery/frame/runtime/svg.js +14 -0
- package/dist/gallery/frame/runtime/tsx.js +52 -0
- package/dist/gallery/index.html +104 -0
- package/docs/reference/cli.md +163 -0
- package/docs/reference/export.md +106 -0
- package/docs/reference/html.md +215 -0
- package/docs/reference/http.md +85 -0
- package/docs/reference/mcp.md +78 -0
- package/docs/reference/security.md +119 -0
- package/docs/reference/storage.md +93 -0
- package/docs/reference/tsx.md +101 -0
- package/docs/reference/validation.md +212 -0
- package/package.json +89 -0
- package/scripts/build-gallery.ts +127 -0
- package/scripts/launch-netns.sh +67 -0
- package/skills/facet/SKILL.md +100 -0
- package/src/cli/client.ts +311 -0
- package/src/cli/commands/create.ts +49 -0
- package/src/cli/commands/doctor.ts +249 -0
- package/src/cli/commands/export.ts +179 -0
- package/src/cli/commands/instantiate.ts +35 -0
- package/src/cli/commands/list.ts +49 -0
- package/src/cli/commands/open.ts +75 -0
- package/src/cli/commands/pin.ts +39 -0
- package/src/cli/commands/promote.ts +49 -0
- package/src/cli/commands/publish.ts +122 -0
- package/src/cli/commands/read-back.ts +56 -0
- package/src/cli/commands/status.ts +114 -0
- package/src/cli/commands/watch.ts +146 -0
- package/src/cli/main.ts +522 -0
- package/src/cli/output.ts +106 -0
- package/src/cli/parser.ts +376 -0
- package/src/cli/presenter.ts +212 -0
- package/src/cli/service-metadata.ts +134 -0
- package/src/cli/spawn-service.ts +326 -0
- package/src/css.d.ts +4 -0
- package/src/gallery-web/app.ts +1581 -0
- package/src/gallery-web/export-menu.ts +121 -0
- package/src/gallery-web/export.ts +112 -0
- package/src/gallery-web/favicon.ts +54 -0
- package/src/gallery-web/frame/entries/chart.ts +6 -0
- package/src/gallery-web/frame/entries/html.ts +6 -0
- package/src/gallery-web/frame/entries/markdown.ts +6 -0
- package/src/gallery-web/frame/entries/mermaid.ts +6 -0
- package/src/gallery-web/frame/entries/svg.ts +6 -0
- package/src/gallery-web/frame/entries/tsx.ts +6 -0
- package/src/gallery-web/frame/frame-payload.ts +91 -0
- package/src/gallery-web/frame/renderer-validation.ts +13 -0
- package/src/gallery-web/frame/renderers/chart.ts +182 -0
- package/src/gallery-web/frame/renderers/dompurify-shim.ts +112 -0
- package/src/gallery-web/frame/renderers/html.ts +48 -0
- package/src/gallery-web/frame/renderers/markdown.ts +81 -0
- package/src/gallery-web/frame/renderers/mermaid.ts +120 -0
- package/src/gallery-web/frame/renderers/registry.ts +260 -0
- package/src/gallery-web/frame/renderers/svg.ts +393 -0
- package/src/gallery-web/frame/renderers/tsx.ts +61 -0
- package/src/gallery-web/frame/runtime.ts +501 -0
- package/src/gallery-web/frame/styles/artifact.css +8424 -0
- package/src/gallery-web/frame/styles/frame.css +183 -0
- package/src/gallery-web/frame/styles/html-source.css +23 -0
- package/src/gallery-web/frame/view-box.ts +33 -0
- package/src/gallery-web/frame-html.ts +72 -0
- package/src/gallery-web/index.html +104 -0
- package/src/gallery-web/session.ts +107 -0
- package/src/gallery-web/sse-client.ts +212 -0
- package/src/gallery-web/styles/app.css +420 -0
- package/src/gallery-web/styles/tokens.css +112 -0
- package/src/gallery-web/styles/verdict.css +186 -0
- package/src/gallery-web/swap.ts +64 -0
- package/src/gallery-web/theme.ts +40 -0
- package/src/gallery-web/view-state.ts +93 -0
- package/src/harness-adapters/claude-code/README.md +10 -0
- package/src/harness-adapters/claude-code/facet.sh +4 -0
- package/src/harness-adapters/codex/README.md +10 -0
- package/src/harness-adapters/codex/facet.sh +4 -0
- package/src/harness-adapters/mcp/README.md +13 -0
- package/src/harness-adapters/mcp/cli-bridge.ts +216 -0
- package/src/harness-adapters/mcp/main.ts +15 -0
- package/src/harness-adapters/mcp/server.ts +164 -0
- package/src/harness-adapters/mcp/tool-schemas.ts +53 -0
- package/src/harness-adapters/opencode/README.md +10 -0
- package/src/harness-adapters/opencode/facet.sh +4 -0
- package/src/runtime/compiled-entrypoints.ts +69 -0
- package/src/service/dispatcher.ts +717 -0
- package/src/service/export.ts +218 -0
- package/src/service/http-utils.ts +55 -0
- package/src/service/lexical/expectations.ts +210 -0
- package/src/service/lifecycle/idle-controller.ts +125 -0
- package/src/service/lifecycle/orphan-cleanup.ts +128 -0
- package/src/service/lifecycle/process-lock.ts +235 -0
- package/src/service/main.ts +136 -0
- package/src/service/process.ts +240 -0
- package/src/service/router-guards.ts +239 -0
- package/src/service/router.ts +627 -0
- package/src/service/security/auth.ts +168 -0
- package/src/service/security/host-origin.ts +138 -0
- package/src/service/security/http-guards.ts +13 -0
- package/src/service/security/leases.ts +182 -0
- package/src/service/security/token-store.ts +138 -0
- package/src/service/server.ts +356 -0
- package/src/service/store/database.ts +61 -0
- package/src/service/store/evidence-retention.ts +234 -0
- package/src/service/store/migrations.ts +167 -0
- package/src/service/store/repository-lifecycle.ts +121 -0
- package/src/service/store/repository.ts +732 -0
- package/src/service/store/schema.ts +218 -0
- package/src/service/stored-verdict.ts +97 -0
- package/src/service/stream.ts +301 -0
- package/src/service/verdict-enrichment.ts +45 -0
- package/src/shared/build/frame-bundle-plugins.ts +61 -0
- package/src/shared/build-mode.ts +28 -0
- package/src/shared/config/evidence-read.ts +98 -0
- package/src/shared/config/limits.ts +56 -0
- package/src/shared/config/paths.ts +96 -0
- package/src/shared/contracts/artifact-types.ts +2 -0
- package/src/shared/contracts/artifact.ts +98 -0
- package/src/shared/contracts/commands/_shared.ts +59 -0
- package/src/shared/contracts/commands/guards.ts +86 -0
- package/src/shared/contracts/commands/index.ts +204 -0
- package/src/shared/contracts/commands/names.ts +35 -0
- package/src/shared/contracts/commands/requests.ts +149 -0
- package/src/shared/contracts/commands/results.ts +200 -0
- package/src/shared/contracts/envelope.ts +140 -0
- package/src/shared/contracts/events.ts +41 -0
- package/src/shared/contracts/observed-counts.ts +19 -0
- package/src/shared/contracts/renderers.ts +6 -0
- package/src/shared/contracts/validation.ts +296 -0
- package/src/shared/errors/facet-error.ts +127 -0
- package/src/shared/errors/store-error.ts +68 -0
- package/src/shared/evidence-image.ts +37 -0
- package/src/shared/export.ts +31 -0
- package/src/shared/html/policy.ts +108 -0
- package/src/shared/html/style-vocabulary.ts +352 -0
- package/src/shared/logging/logger.ts +108 -0
- package/src/shared/logging/redact.ts +49 -0
- package/src/shared/security/frozen-csp.ts +13 -0
- package/src/shared/storage-version.ts +2 -0
- package/src/shared/tsx/execution.ts +29 -0
- package/src/shared/tsx/import-policy.ts +94 -0
- package/src/shared/util/dir-permissions.ts +94 -0
- package/src/shared/util/mermaid-nodes.ts +230 -0
- package/src/shared/util/process.ts +67 -0
- package/src/shared/util/time.ts +21 -0
- package/src/shared/version.ts +3 -0
- package/src/validation/sandbox/limits.ts +41 -0
- package/src/validation/sandbox/netns.ts +177 -0
- package/src/validation/tier0/chart.ts +177 -0
- package/src/validation/tier0/dom-shim.ts +54 -0
- package/src/validation/tier0/html.ts +408 -0
- package/src/validation/tier0/markdown.ts +285 -0
- package/src/validation/tier0/mermaid.ts +98 -0
- package/src/validation/tier0/runner.ts +504 -0
- package/src/validation/tier0/svg.ts +321 -0
- package/src/validation/tier0/tsx/allowlist-resolver.ts +77 -0
- package/src/validation/tier0/tsx/ast-policy-capabilities.ts +238 -0
- package/src/validation/tier0/tsx/ast-policy-shared.ts +71 -0
- package/src/validation/tier0/tsx/ast-policy.ts +181 -0
- package/src/validation/tier0/tsx/compiler.ts +156 -0
- package/src/validation/tier0/worker-dispatch.ts +211 -0
- package/src/validation/tier0/worker-entry.ts +96 -0
- package/src/validation/tier0/worker-input.ts +75 -0
- package/src/validation/tier1/browser-process.ts +147 -0
- package/src/validation/tier1/cdp-pipe.ts +281 -0
- package/src/validation/tier1/entries/chart.ts +5 -0
- package/src/validation/tier1/entries/html.ts +6 -0
- package/src/validation/tier1/entries/markdown.ts +5 -0
- package/src/validation/tier1/entries/mermaid.ts +5 -0
- package/src/validation/tier1/entries/svg.ts +5 -0
- package/src/validation/tier1/entries/tsx.ts +5 -0
- package/src/validation/tier1/frame-target.ts +162 -0
- package/src/validation/tier1/harness-entry.ts +261 -0
- package/src/validation/tier1/harness.ts +215 -0
- package/src/validation/tier1/isolated-probe.ts +94 -0
- package/src/validation/tier1/launcher.ts +177 -0
- package/src/validation/tier1/limits.ts +80 -0
- package/src/validation/tier1/nonce.ts +17 -0
- package/src/validation/tier1/protocol-probe.ts +519 -0
- package/src/validation/tier1/runner.ts +1122 -0
- package/src/validation/tier1/verdict.ts +246 -0
- package/src/validation/tier1/webp.ts +69 -0
- package/templates/README.md +88 -0
- package/templates/bar-compare.vl.json +63 -0
- package/templates/capacity-report.tsx +102 -0
- package/templates/decision-record.md +24 -0
- package/templates/deployment-state.mmd +40 -0
- package/templates/exemplar.md +91 -0
- package/templates/facet-story.tsx +799 -0
- package/templates/fleet-dashboard.html +141 -0
- package/templates/html-release-ledger.html +91 -0
- package/templates/html-status-report.html +114 -0
- package/templates/incident-console.tsx +147 -0
- package/templates/legion-boundaries.mmd +23 -0
- package/templates/legion-flow.mmd +15 -0
- package/templates/legion-sequence.mmd +14 -0
- package/templates/legion-state.mmd +7 -0
- package/templates/metric-card.svg +29 -0
- package/templates/observability-map.svg +101 -0
- package/templates/pipeline-audit.md +108 -0
- package/templates/release-metrics.vl.json +98 -0
- package/templates/service-topology.mmd +47 -0
- package/templates/status-report.md +32 -0
- package/templates/system-map.svg +53 -0
- package/templates/timeseries.vl.json +71 -0
- package/templates/tsx-interactive-counter.tsx +19 -0
- package/templates/tsx-status-report.tsx +31 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated SSE client for the gallery shell.
|
|
3
|
+
*
|
|
4
|
+
* The service's `/api/v1/stream` route emits `revision:committed`
|
|
5
|
+
* events for the artifact bound to the current gallery lease. The
|
|
6
|
+
* shell must authenticate every connect with `Authorization: Bearer`
|
|
7
|
+
* + `X-Gallery-Lease` + `X-Gallery-Artifact` headers — EventSource
|
|
8
|
+
* cannot set custom headers, so we use `fetch()` + ReadableStream
|
|
9
|
+
* chunk-by-chunk parsing instead.
|
|
10
|
+
*
|
|
11
|
+
* On a `revision:committed` event the shell:
|
|
12
|
+
* 1. Reacquires a fresh lease via the `open` command (rotated).
|
|
13
|
+
* 2. Fetches the exact revision bytes.
|
|
14
|
+
* 3. Triggers `replaceArtifactFrame` (double-buffered HMR swap).
|
|
15
|
+
*
|
|
16
|
+
* Reconnect: on transient network failure the client retries with a
|
|
17
|
+
* fresh lease. The lease id is rotated on every reconnect so a stale
|
|
18
|
+
* lease cannot leak via the SSE transport.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface ConnectRevisionStreamOptions {
|
|
22
|
+
readonly baseUrl: string;
|
|
23
|
+
readonly bearer: string;
|
|
24
|
+
readonly leaseId: string;
|
|
25
|
+
readonly artifactId: string;
|
|
26
|
+
/** Called for every `revision:committed` event. */
|
|
27
|
+
readonly onCommit: (event: {
|
|
28
|
+
readonly artifactId: string;
|
|
29
|
+
readonly revisionSha: string;
|
|
30
|
+
readonly revisionNumber: number;
|
|
31
|
+
readonly artifactType: string;
|
|
32
|
+
readonly at: string;
|
|
33
|
+
}) => void;
|
|
34
|
+
/** Connection lifecycle state for the gallery shell indicator. */
|
|
35
|
+
readonly onState?: (state: "idle" | "connecting" | "live") => void;
|
|
36
|
+
/** Optional: called for `stream:heartbeat` events (used by tests). */
|
|
37
|
+
readonly onHeartbeat?: (event: { readonly streamId: string; readonly at: string }) => void;
|
|
38
|
+
/** Optional: called on `stream:close`. */
|
|
39
|
+
readonly onClose?: (event: {
|
|
40
|
+
readonly streamId: string;
|
|
41
|
+
readonly reason: string;
|
|
42
|
+
readonly at: string;
|
|
43
|
+
}) => void;
|
|
44
|
+
/** Abort signal — caller cancels to tear down the stream. */
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
/** Hostname guard — fires before the fetch. */
|
|
47
|
+
readonly hostname: string;
|
|
48
|
+
/** Inject for tests. Defaults to global fetch. */
|
|
49
|
+
readonly fetchImpl?: typeof fetch;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ConnectRevisionStreamHandle {
|
|
53
|
+
/** Cancels the underlying stream + aborts any in-flight fetch. */
|
|
54
|
+
close(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const HEARTBEAT_PREFIX = ":";
|
|
58
|
+
const MAX_RECONNECT_ATTEMPTS = 3;
|
|
59
|
+
const RECONNECT_DELAY_MS = 100;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Connect to the revision SSE stream. Returns a handle whose `close()`
|
|
63
|
+
* tears down the connection. The handler MUST run on 127.0.0.1 — a
|
|
64
|
+
* non-loopback hostname is rejected before the fetch starts.
|
|
65
|
+
*/
|
|
66
|
+
export function connectRevisionStream(
|
|
67
|
+
options: ConnectRevisionStreamOptions,
|
|
68
|
+
): ConnectRevisionStreamHandle {
|
|
69
|
+
if (options.hostname !== "127.0.0.1") {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Refusing to connect SSE: hostname must be 127.0.0.1 (got: ${JSON.stringify(options.hostname)})`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const fetcher = options.fetchImpl ?? fetch;
|
|
75
|
+
const url = `${options.baseUrl.replace(/\/$/, "")}/api/v1/stream`;
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
options.onState?.("connecting");
|
|
78
|
+
if (options.signal !== undefined) {
|
|
79
|
+
if (options.signal.aborted) controller.abort();
|
|
80
|
+
else options.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
81
|
+
}
|
|
82
|
+
let closed = false;
|
|
83
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
|
84
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
85
|
+
let reconnectAttempt = 0;
|
|
86
|
+
const closeWithReason = (reason: string): void => {
|
|
87
|
+
options.onState?.("idle");
|
|
88
|
+
options.onClose?.({ streamId: "", reason, at: new Date().toISOString() });
|
|
89
|
+
};
|
|
90
|
+
const reconnect = (reason: string): void => {
|
|
91
|
+
if (closed) return;
|
|
92
|
+
if (reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
|
93
|
+
closeWithReason(reason);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const delay = RECONNECT_DELAY_MS * 2 ** reconnectAttempt;
|
|
97
|
+
reconnectAttempt += 1;
|
|
98
|
+
options.onState?.("connecting");
|
|
99
|
+
reconnectTimer = setTimeout(() => {
|
|
100
|
+
reconnectTimer = null;
|
|
101
|
+
void connect();
|
|
102
|
+
}, delay);
|
|
103
|
+
};
|
|
104
|
+
const connect = async (): Promise<void> => {
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetcher(url, {
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: {
|
|
109
|
+
authorization: `Bearer ${options.bearer}`,
|
|
110
|
+
"x-gallery-lease": options.leaseId,
|
|
111
|
+
"x-gallery-artifact": options.artifactId,
|
|
112
|
+
accept: "text/event-stream",
|
|
113
|
+
},
|
|
114
|
+
signal: controller.signal,
|
|
115
|
+
});
|
|
116
|
+
if (response.status !== 200 || response.body === null) {
|
|
117
|
+
const reason = `stream_status_${response.status}`;
|
|
118
|
+
if (response.status === 401) closeWithReason(reason);
|
|
119
|
+
else reconnect(reason);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
reconnectAttempt = 0;
|
|
123
|
+
options.onState?.("live");
|
|
124
|
+
reader = response.body.getReader();
|
|
125
|
+
const decoder = new TextDecoder();
|
|
126
|
+
let buffer = "";
|
|
127
|
+
// Read chunk-by-chunk; SSE events are `data: <json>\n\n` blocks
|
|
128
|
+
// separated by blank lines. Heartbeats are `: heartbeat\n\n`.
|
|
129
|
+
// The `reader.read()` promise rejects when `close()` aborts the
|
|
130
|
+
// controller, which is the cancellation signal — break out of
|
|
131
|
+
// the loop without setting `closed` from inside.
|
|
132
|
+
for (;;) {
|
|
133
|
+
if (closed) break;
|
|
134
|
+
const next = await reader.read().catch(() => ({ value: undefined, done: true }));
|
|
135
|
+
const value = next.value;
|
|
136
|
+
const done = next.done;
|
|
137
|
+
if (done || value === undefined) break;
|
|
138
|
+
buffer += decoder.decode(value, { stream: true });
|
|
139
|
+
let blockEnd = buffer.indexOf("\n\n");
|
|
140
|
+
while (blockEnd >= 0) {
|
|
141
|
+
const block = buffer.slice(0, blockEnd);
|
|
142
|
+
buffer = buffer.slice(blockEnd + 2);
|
|
143
|
+
blockEnd = buffer.indexOf("\n\n");
|
|
144
|
+
if (block.length === 0) continue;
|
|
145
|
+
if (block.startsWith(HEARTBEAT_PREFIX)) continue;
|
|
146
|
+
if (!block.startsWith("data: ")) continue;
|
|
147
|
+
const payloadText = block.slice("data: ".length).trim();
|
|
148
|
+
if (payloadText.length === 0) continue;
|
|
149
|
+
let parsed: unknown;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(payloadText);
|
|
152
|
+
} catch {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
dispatch(options, parsed);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!closed) reconnect("stream_closed");
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (controller.signal.aborted) return;
|
|
161
|
+
reconnect(error instanceof Error ? error.message : "stream_error");
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
void connect();
|
|
165
|
+
return {
|
|
166
|
+
close(): void {
|
|
167
|
+
if (closed) return;
|
|
168
|
+
closed = true;
|
|
169
|
+
if (reconnectTimer !== null) clearTimeout(reconnectTimer);
|
|
170
|
+
try {
|
|
171
|
+
controller.abort();
|
|
172
|
+
} catch {
|
|
173
|
+
// already aborted
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
reader?.cancel().catch(() => undefined);
|
|
177
|
+
} catch {
|
|
178
|
+
// already cancelled
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function dispatch(options: ConnectRevisionStreamOptions, parsed: unknown): void {
|
|
185
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
186
|
+
const record = parsed as Record<string, unknown>;
|
|
187
|
+
if (record["type"] === "revision:committed") {
|
|
188
|
+
options.onCommit({
|
|
189
|
+
artifactId: String(record["artifactId"] ?? ""),
|
|
190
|
+
revisionSha: String(record["revisionSha"] ?? ""),
|
|
191
|
+
revisionNumber: Number(record["revisionNumber"] ?? 0),
|
|
192
|
+
artifactType: String(record["artifactType"] ?? ""),
|
|
193
|
+
at: String(record["at"] ?? new Date().toISOString()),
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (record["type"] === "stream:heartbeat") {
|
|
198
|
+
options.onHeartbeat?.({
|
|
199
|
+
streamId: String(record["streamId"] ?? ""),
|
|
200
|
+
at: String(record["at"] ?? new Date().toISOString()),
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (record["type"] === "stream:close") {
|
|
205
|
+
options.onClose?.({
|
|
206
|
+
streamId: String(record["streamId"] ?? ""),
|
|
207
|
+
reason: String(record["reason"] ?? ""),
|
|
208
|
+
at: String(record["at"] ?? new Date().toISOString()),
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Gallery shell layout — verification-instrument treatment.
|
|
3
|
+
*
|
|
4
|
+
* Same three-band structure as before (identity → stage → controls)
|
|
5
|
+
* plus an optional evidence strip under the header. Every selector
|
|
6
|
+
* app.ts touches is preserved: #facet-status, .title, .revision,
|
|
7
|
+
* .status-line, #facet-canvas, iframe.facet-hidden / .facet-ready,
|
|
8
|
+
* #facet-controls button, #facet-error.facet-visible. New elements
|
|
9
|
+
* are additive ids only.
|
|
10
|
+
*
|
|
11
|
+
* Surfaces stay opaque — this is a data instrument, not glass. No
|
|
12
|
+
* remote assets; system-stack fallback; glyphs are plain Unicode.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
margin: 0;
|
|
17
|
+
background: var(--facet-surface);
|
|
18
|
+
color: var(--facet-text);
|
|
19
|
+
font-family: var(--facet-font-sans);
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
line-height: 1.45;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#facet-shell {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-rows: auto auto 1fr auto;
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ---- header · identity left, testimony right ---- */
|
|
31
|
+
|
|
32
|
+
#facet-status {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 14px;
|
|
36
|
+
height: 52px;
|
|
37
|
+
padding: 0 20px;
|
|
38
|
+
border-bottom: 1px solid var(--facet-rule-semantic);
|
|
39
|
+
background: var(--facet-surface-raised);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#facet-mark {
|
|
43
|
+
color: var(--facet-cyan);
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
transform: translateY(-0.5px);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#facet-status .title {
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
color: var(--facet-text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#facet-status .sep {
|
|
54
|
+
width: 1px;
|
|
55
|
+
height: 18px;
|
|
56
|
+
background: var(--facet-rule-semantic);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#facet-artifact-title {
|
|
60
|
+
color: var(--facet-text-muted);
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
}
|
|
63
|
+
#facet-artifact-title:empty {
|
|
64
|
+
display: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#facet-status .revision {
|
|
68
|
+
font-family: var(--facet-font-mono);
|
|
69
|
+
color: var(--facet-cyan);
|
|
70
|
+
font-size: 11.5px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Display-state readout (T2 — display, never a verdict). */
|
|
74
|
+
#facet-status .status-line {
|
|
75
|
+
margin-left: auto;
|
|
76
|
+
color: var(--facet-text-faint);
|
|
77
|
+
font-family: var(--facet-font-mono);
|
|
78
|
+
font-size: 11px;
|
|
79
|
+
}
|
|
80
|
+
#facet-status .status-line::after {
|
|
81
|
+
content: " · T2";
|
|
82
|
+
opacity: 0.6;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ---- evidence strip (hidden until a verdict is loaded) ---- */
|
|
86
|
+
|
|
87
|
+
#facet-evidence {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 22px;
|
|
91
|
+
padding: 9px 20px;
|
|
92
|
+
background: var(--facet-surface-raised);
|
|
93
|
+
border-bottom: 1px solid var(--facet-rule-semantic);
|
|
94
|
+
font-family: var(--facet-font-mono);
|
|
95
|
+
font-size: 11.5px;
|
|
96
|
+
color: var(--facet-text-muted);
|
|
97
|
+
}
|
|
98
|
+
#facet-evidence[hidden] {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
#facet-evidence .label {
|
|
102
|
+
color: var(--facet-text-faint);
|
|
103
|
+
font-size: 10px;
|
|
104
|
+
letter-spacing: 0.1em;
|
|
105
|
+
}
|
|
106
|
+
#facet-evidence .sha {
|
|
107
|
+
color: var(--facet-cyan);
|
|
108
|
+
}
|
|
109
|
+
#facet-evidence .divergence {
|
|
110
|
+
color: var(--facet-error);
|
|
111
|
+
}
|
|
112
|
+
#facet-evidence .binding {
|
|
113
|
+
margin-left: auto;
|
|
114
|
+
color: var(--facet-text-faint);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ---- canvas · sunken stage ---- */
|
|
118
|
+
|
|
119
|
+
/* The stage IS the window: the artifact frame fills every pixel between
|
|
120
|
+
* header and footer. No inset card, no centering, no size cap — the
|
|
121
|
+
* frame's own document scrolls its overflow (operator-ruled, twice). */
|
|
122
|
+
#facet-canvas {
|
|
123
|
+
position: relative;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
background: var(--facet-surface-stage);
|
|
126
|
+
/* border-box: the reserved padding below must come OUT of the grid's
|
|
127
|
+
allocated `1fr` row height, not add to it — content-box padding
|
|
128
|
+
would grow the canvas's total footprint by the footer's height
|
|
129
|
+
and reintroduce the same off-viewport overflow this fix targets. */
|
|
130
|
+
box-sizing: border-box;
|
|
131
|
+
/* Reserve space for the now-fixed #facet-controls footer (its own
|
|
132
|
+
height, kept in sync above) so artifact content is never hidden
|
|
133
|
+
under it. */
|
|
134
|
+
padding-bottom: 44px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Double-buffer progress hairline — position carries the signal, so
|
|
138
|
+
it also shows (without motion) under reduced motion. */
|
|
139
|
+
#facet-swapbar {
|
|
140
|
+
position: absolute;
|
|
141
|
+
top: 0;
|
|
142
|
+
left: 0;
|
|
143
|
+
right: 0;
|
|
144
|
+
height: 2px;
|
|
145
|
+
background: color-mix(in srgb, var(--facet-focus) 12%, transparent);
|
|
146
|
+
}
|
|
147
|
+
#facet-swapbar[hidden] {
|
|
148
|
+
display: none;
|
|
149
|
+
}
|
|
150
|
+
#facet-swapbar .bar {
|
|
151
|
+
width: 34%;
|
|
152
|
+
height: 100%;
|
|
153
|
+
background: var(--facet-cyan);
|
|
154
|
+
transition: width 200ms ease-out;
|
|
155
|
+
}
|
|
156
|
+
#facet-swapbar[data-failed] .bar {
|
|
157
|
+
background: var(--facet-error);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#facet-empty {
|
|
161
|
+
display: flex;
|
|
162
|
+
flex-direction: column;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: 14px;
|
|
165
|
+
text-align: center;
|
|
166
|
+
}
|
|
167
|
+
#facet-empty[hidden] {
|
|
168
|
+
display: none;
|
|
169
|
+
}
|
|
170
|
+
#facet-empty .glyph {
|
|
171
|
+
font-size: 26px;
|
|
172
|
+
color: var(--facet-text-faint);
|
|
173
|
+
}
|
|
174
|
+
#facet-empty .head {
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
font-size: 15px;
|
|
177
|
+
color: var(--facet-text);
|
|
178
|
+
}
|
|
179
|
+
#facet-empty .sub {
|
|
180
|
+
font-size: 13px;
|
|
181
|
+
color: var(--facet-text-faint);
|
|
182
|
+
}
|
|
183
|
+
#facet-empty .cmd {
|
|
184
|
+
font-family: var(--facet-font-mono);
|
|
185
|
+
font-size: 12px;
|
|
186
|
+
color: var(--facet-text-muted);
|
|
187
|
+
background: var(--facet-surface);
|
|
188
|
+
border: 1px solid var(--facet-rule-semantic);
|
|
189
|
+
border-radius: 6px;
|
|
190
|
+
padding: 8px 14px;
|
|
191
|
+
}
|
|
192
|
+
#facet-empty .cmd .p {
|
|
193
|
+
color: var(--facet-text-faint);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#facet-expired {
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: 14px;
|
|
201
|
+
text-align: center;
|
|
202
|
+
}
|
|
203
|
+
#facet-expired[hidden] {
|
|
204
|
+
display: none;
|
|
205
|
+
}
|
|
206
|
+
#facet-expired .glyph {
|
|
207
|
+
font-size: 26px;
|
|
208
|
+
color: var(--facet-error);
|
|
209
|
+
}
|
|
210
|
+
#facet-expired .head {
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
font-size: 15px;
|
|
213
|
+
color: var(--facet-text);
|
|
214
|
+
}
|
|
215
|
+
#facet-expired .sub {
|
|
216
|
+
font-size: 13px;
|
|
217
|
+
color: var(--facet-text-faint);
|
|
218
|
+
}
|
|
219
|
+
#facet-expired .cmd {
|
|
220
|
+
font-family: var(--facet-font-mono);
|
|
221
|
+
font-size: 12px;
|
|
222
|
+
color: var(--facet-text-muted);
|
|
223
|
+
background: var(--facet-surface);
|
|
224
|
+
border: 1px solid var(--facet-rule-semantic);
|
|
225
|
+
border-radius: 6px;
|
|
226
|
+
padding: 8px 14px;
|
|
227
|
+
}
|
|
228
|
+
#facet-expired .cmd .p {
|
|
229
|
+
color: var(--facet-text-faint);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#facet-canvas iframe {
|
|
233
|
+
border: 0;
|
|
234
|
+
background: var(--facet-surface);
|
|
235
|
+
transform-origin: center center;
|
|
236
|
+
transition:
|
|
237
|
+
transform 120ms ease-out,
|
|
238
|
+
opacity 120ms linear;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#facet-canvas iframe.facet-hidden {
|
|
242
|
+
position: absolute;
|
|
243
|
+
width: 0;
|
|
244
|
+
height: 0;
|
|
245
|
+
opacity: 0;
|
|
246
|
+
pointer-events: none;
|
|
247
|
+
border: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#facet-canvas iframe.facet-ready {
|
|
251
|
+
display: block;
|
|
252
|
+
width: 100%;
|
|
253
|
+
height: 100%;
|
|
254
|
+
opacity: 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* ---- footer · connection left, controls right ---- */
|
|
258
|
+
|
|
259
|
+
/*
|
|
260
|
+
* Anchored to the visual viewport, not the document flow — at extreme
|
|
261
|
+
* small viewports (400x300) whatever pushes the grid's `1fr` canvas
|
|
262
|
+
* row past its available height carries the whole document past
|
|
263
|
+
* 100vh, and an in-flow footer rides off the bottom edge with it
|
|
264
|
+
* (recorded failure: every control landed at y=381 in a 300px-tall
|
|
265
|
+
* viewport). `position: fixed` decouples the toolbar from that
|
|
266
|
+
* document height entirely — it always sits at the visual viewport's
|
|
267
|
+
* own bottom edge. The grid still reserves the `auto` row for it (an
|
|
268
|
+
* out-of-flow item's track collapses to 0, so nothing double-counts),
|
|
269
|
+
* and the canvas gets matching bottom padding so fixed positioning
|
|
270
|
+
* never covers artifact content.
|
|
271
|
+
*/
|
|
272
|
+
#facet-controls {
|
|
273
|
+
position: fixed;
|
|
274
|
+
left: 0;
|
|
275
|
+
right: 0;
|
|
276
|
+
bottom: 0;
|
|
277
|
+
z-index: 5;
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
gap: 8px;
|
|
281
|
+
height: 44px;
|
|
282
|
+
padding: 0 20px;
|
|
283
|
+
border-top: 1px solid var(--facet-rule-semantic);
|
|
284
|
+
background: var(--facet-surface-raised);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#facet-live {
|
|
288
|
+
display: inline-flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: 7px;
|
|
291
|
+
font-family: var(--facet-font-mono);
|
|
292
|
+
font-size: 11px;
|
|
293
|
+
color: var(--facet-text-faint);
|
|
294
|
+
}
|
|
295
|
+
#facet-live .dot {
|
|
296
|
+
font-size: 9px;
|
|
297
|
+
color: var(--facet-text-faint);
|
|
298
|
+
}
|
|
299
|
+
#facet-live[data-state="live"] .dot {
|
|
300
|
+
color: var(--facet-live-ok);
|
|
301
|
+
}
|
|
302
|
+
#facet-live[data-state="connecting"] .dot {
|
|
303
|
+
color: var(--facet-live-partial);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#facet-controls .spacer {
|
|
307
|
+
margin-left: auto;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#facet-controls button {
|
|
311
|
+
height: 28px;
|
|
312
|
+
min-width: 28px;
|
|
313
|
+
padding: 0 10px;
|
|
314
|
+
display: grid;
|
|
315
|
+
place-items: center;
|
|
316
|
+
background: var(--facet-surface-control);
|
|
317
|
+
color: var(--facet-text);
|
|
318
|
+
border: 1px solid var(--facet-rule-semantic);
|
|
319
|
+
border-radius: 6px;
|
|
320
|
+
font-family: var(--facet-font-mono);
|
|
321
|
+
font-size: 11.5px;
|
|
322
|
+
cursor: pointer;
|
|
323
|
+
transition:
|
|
324
|
+
border-color 120ms ease-out,
|
|
325
|
+
color 120ms ease-out;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#facet-controls button:hover {
|
|
329
|
+
border-color: color-mix(in srgb, var(--facet-focus) 48%, transparent);
|
|
330
|
+
color: var(--facet-focus);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#facet-controls button:focus-visible {
|
|
334
|
+
outline: 2px solid var(--facet-focus);
|
|
335
|
+
outline-offset: 2px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#facet-controls button:active {
|
|
339
|
+
transform: scale(0.98);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Clamp-bound feedback — a disabled zoom-in/out button reads as a
|
|
343
|
+
no-op instead of silently eating clicks at 0.25x / 8x. */
|
|
344
|
+
#facet-controls button:disabled {
|
|
345
|
+
opacity: 0.4;
|
|
346
|
+
cursor: default;
|
|
347
|
+
color: var(--facet-text-faint);
|
|
348
|
+
}
|
|
349
|
+
#facet-controls button:disabled:hover {
|
|
350
|
+
border-color: var(--facet-rule-semantic);
|
|
351
|
+
color: var(--facet-text-faint);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* Latched state for the pan/zoom mode toggle — the only toolbar
|
|
355
|
+
button with persistent on/off state rather than a momentary action. */
|
|
356
|
+
#facet-controls button[aria-pressed="true"] {
|
|
357
|
+
background: var(--facet-focus);
|
|
358
|
+
color: var(--facet-surface-raised);
|
|
359
|
+
border-color: var(--facet-focus);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.facet-export {
|
|
363
|
+
position: relative;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#facet-export-menu {
|
|
367
|
+
position: absolute;
|
|
368
|
+
right: 0;
|
|
369
|
+
bottom: 36px;
|
|
370
|
+
z-index: 6;
|
|
371
|
+
display: grid;
|
|
372
|
+
gap: 4px;
|
|
373
|
+
min-width: 120px;
|
|
374
|
+
padding: 6px;
|
|
375
|
+
border: 1px solid var(--facet-rule-semantic);
|
|
376
|
+
border-radius: 6px;
|
|
377
|
+
background: var(--facet-surface-raised);
|
|
378
|
+
box-shadow: 0 8px 24px var(--facet-shadow-export);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#facet-export-menu[hidden] {
|
|
382
|
+
display: none;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#facet-export-menu button {
|
|
386
|
+
width: 100%;
|
|
387
|
+
justify-content: start;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* Display-layer failure chip — an event, not a verdict, so it takes
|
|
391
|
+
the outline treatment (only `tampered` ever fills). */
|
|
392
|
+
#facet-error {
|
|
393
|
+
display: none;
|
|
394
|
+
align-items: center;
|
|
395
|
+
gap: 7px;
|
|
396
|
+
margin-left: 10px;
|
|
397
|
+
height: 24px;
|
|
398
|
+
padding: 0 11px;
|
|
399
|
+
border: 1px solid var(--facet-fail-border);
|
|
400
|
+
background: var(--facet-fail-fill);
|
|
401
|
+
color: var(--facet-error);
|
|
402
|
+
border-radius: 999px;
|
|
403
|
+
font-family: var(--facet-font-mono);
|
|
404
|
+
font-size: 11px;
|
|
405
|
+
}
|
|
406
|
+
#facet-error::before {
|
|
407
|
+
content: "✗";
|
|
408
|
+
}
|
|
409
|
+
#facet-error.facet-visible {
|
|
410
|
+
display: inline-flex;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* Respect prefers-reduced-motion — swaps become instant. */
|
|
414
|
+
@media (prefers-reduced-motion: reduce) {
|
|
415
|
+
#facet-canvas iframe,
|
|
416
|
+
#facet-swapbar .bar,
|
|
417
|
+
#facet-controls button {
|
|
418
|
+
transition: none;
|
|
419
|
+
}
|
|
420
|
+
}
|