@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,183 @@
|
|
|
1
|
+
html,
|
|
2
|
+
body {
|
|
3
|
+
margin: 0;
|
|
4
|
+
height: 100%;
|
|
5
|
+
background: var(--facet-frame-surface);
|
|
6
|
+
color: var(--facet-frame-text);
|
|
7
|
+
font:
|
|
8
|
+
14px/1.55 ui-sans-serif,
|
|
9
|
+
system-ui,
|
|
10
|
+
-apple-system,
|
|
11
|
+
"Segoe UI",
|
|
12
|
+
Roboto,
|
|
13
|
+
sans-serif;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
:root {
|
|
17
|
+
--facet-frame-surface: #151823;
|
|
18
|
+
--facet-frame-stage: #151823;
|
|
19
|
+
--facet-frame-text: #c8d3f5;
|
|
20
|
+
--facet-frame-muted: #a9b8e8;
|
|
21
|
+
--facet-frame-faint: #737aa2;
|
|
22
|
+
--facet-frame-focus: #82aaff;
|
|
23
|
+
--facet-frame-link: #82aaff;
|
|
24
|
+
--facet-frame-code: #c3e88d;
|
|
25
|
+
--facet-frame-code-surface: #1e2030;
|
|
26
|
+
--facet-frame-rule: #2f334d;
|
|
27
|
+
--facet-frame-quote: #a9b8e8;
|
|
28
|
+
--facet-frame-quote-rule: #444a73;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[data-theme="winter"] {
|
|
32
|
+
--facet-frame-surface: #ffffff;
|
|
33
|
+
--facet-frame-stage: #f8fafc;
|
|
34
|
+
--facet-frame-text: #172033;
|
|
35
|
+
--facet-frame-muted: #334155;
|
|
36
|
+
--facet-frame-faint: #475569;
|
|
37
|
+
--facet-frame-focus: #075985;
|
|
38
|
+
--facet-frame-link: #075985;
|
|
39
|
+
--facet-frame-code: #166534;
|
|
40
|
+
--facet-frame-code-surface: #f1f5f9;
|
|
41
|
+
--facet-frame-rule: #cbd5e1;
|
|
42
|
+
--facet-frame-quote: #334155;
|
|
43
|
+
--facet-frame-quote-rule: #64748b;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#artifact {
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
height: 100%;
|
|
49
|
+
margin: 0;
|
|
50
|
+
padding: 16px;
|
|
51
|
+
overflow: auto;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
* Standalone diagrams (mermaid/svg/chart) center on both axes when
|
|
56
|
+
* the rendered root fits the stage. `safe` is load-bearing: it falls
|
|
57
|
+
* back to start-aligned scrolling the moment the root overflows, so
|
|
58
|
+
* zoomed-in or naturally oversized content stays 100% reachable by
|
|
59
|
+
* scroll instead of clipping unreachable off-screen (the regression
|
|
60
|
+
* a plain `center` reintroduces).
|
|
61
|
+
*/
|
|
62
|
+
#artifact[data-facet-artifact-type="mermaid"],
|
|
63
|
+
#artifact[data-facet-artifact-type="svg"],
|
|
64
|
+
#artifact[data-facet-artifact-type="chart"] {
|
|
65
|
+
display: flex;
|
|
66
|
+
justify-content: safe center;
|
|
67
|
+
align-items: safe center;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Markdown reads top-down (no vertical centering) but should not
|
|
72
|
+
* stretch to an unreadable line length at wide viewports: cap the
|
|
73
|
+
* column at a ~92ch measure and center it horizontally. Below that
|
|
74
|
+
* measure the calc() collapses to the original 16px padding, so
|
|
75
|
+
* narrow viewports are unaffected.
|
|
76
|
+
*/
|
|
77
|
+
#artifact[data-facet-artifact-type="markdown"] {
|
|
78
|
+
padding-inline: max(16px, calc((100% - 92ch) / 2));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Native dashboard artifacts (html, interactive tsx) read top-down
|
|
83
|
+
* like markdown — vertical stays start-aligned — but unlike markdown
|
|
84
|
+
* their width is whatever the fixture authored (often a fixed-width
|
|
85
|
+
* `max-w-*` container), not a fluid readable column. Left unstyled,
|
|
86
|
+
* that narrow content sits pinned to the container's left edge,
|
|
87
|
+
* stranding most of a wide desktop stage as dead space.
|
|
88
|
+
*
|
|
89
|
+
* The centering has to land on the renderer-ROOT element
|
|
90
|
+
* (`.facet-html-root` / `#facet-tsx-mount`), not `#artifact` itself:
|
|
91
|
+
* `renderHtml`/`renderTsx` always mount a single full-width wrapper
|
|
92
|
+
* as `#artifact`'s only child, and the fixture's own narrow content
|
|
93
|
+
* (e.g. `<main class="max-w-2xl">`) is a GRANDCHILD of `#artifact`
|
|
94
|
+
* inside that wrapper — centering `#artifact`'s single full-width
|
|
95
|
+
* child does nothing. `safe center` on the horizontal axis only —
|
|
96
|
+
* the same fallback-to-start mechanism the standalone-diagram rule
|
|
97
|
+
* above uses — centers content that fits and never clips content
|
|
98
|
+
* wider than the stage (it start-aligns and stays reachable by
|
|
99
|
+
* scroll instead).
|
|
100
|
+
*/
|
|
101
|
+
#artifact[data-facet-artifact-type="html"] > [data-facet-renderer-root],
|
|
102
|
+
#artifact[data-facet-artifact-type="tsx"] > [data-facet-renderer-root] {
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: safe center;
|
|
105
|
+
align-items: flex-start;
|
|
106
|
+
min-height: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#artifact > svg {
|
|
110
|
+
display: block;
|
|
111
|
+
width: fit-content;
|
|
112
|
+
max-height: none;
|
|
113
|
+
height: auto;
|
|
114
|
+
/* Flex items shrink by default (min-width: auto); without this a
|
|
115
|
+
wide diagram would squeeze toward the flex container's width
|
|
116
|
+
instead of overflowing it — the exact jail regression `safe
|
|
117
|
+
center` above is meant to prevent. */
|
|
118
|
+
flex-shrink: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#artifact > canvas,
|
|
122
|
+
#artifact > iframe {
|
|
123
|
+
display: block;
|
|
124
|
+
flex-shrink: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#artifact [data-facet-diagram-region] {
|
|
128
|
+
display: block;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
outline: 1px solid transparent;
|
|
131
|
+
outline-offset: 2px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#artifact [data-facet-diagram-region][data-facet-diagram-engaged="true"] {
|
|
135
|
+
outline-color: var(--facet-frame-focus);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#artifact [data-facet-diagram-region] > svg {
|
|
139
|
+
display: block;
|
|
140
|
+
max-width: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#artifact a {
|
|
144
|
+
color: var(--facet-frame-link);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#artifact code,
|
|
148
|
+
#artifact pre {
|
|
149
|
+
color: var(--facet-frame-code);
|
|
150
|
+
background: var(--facet-frame-code-surface);
|
|
151
|
+
border-radius: 4px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#artifact code {
|
|
155
|
+
padding: 1px 4px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#artifact pre {
|
|
159
|
+
padding: 10px;
|
|
160
|
+
overflow: auto;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#artifact table {
|
|
164
|
+
border-collapse: collapse;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#artifact th,
|
|
168
|
+
#artifact td {
|
|
169
|
+
padding: 4px 8px;
|
|
170
|
+
border: 1px solid var(--facet-frame-rule);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#artifact blockquote {
|
|
174
|
+
margin: 0 0 0 8px;
|
|
175
|
+
padding-left: 12px;
|
|
176
|
+
color: var(--facet-frame-quote);
|
|
177
|
+
border-left: 3px solid var(--facet-frame-quote-rule);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#artifact hr {
|
|
181
|
+
border: 0;
|
|
182
|
+
border-top: 1px solid var(--facet-frame-rule);
|
|
183
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import "tailwindcss" source(none);
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--color-legion-ink: var(--facet-frame-text);
|
|
5
|
+
--color-legion-muted: var(--facet-frame-muted);
|
|
6
|
+
--color-legion-cyan: var(--facet-frame-link);
|
|
7
|
+
--color-legion-paper: var(--facet-frame-surface);
|
|
8
|
+
--color-legion-line: var(--facet-frame-rule);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
[data-theme="winter"] {
|
|
12
|
+
--color-legion-ink: var(--facet-frame-text);
|
|
13
|
+
--color-legion-muted: var(--facet-frame-muted);
|
|
14
|
+
--color-legion-cyan: var(--facet-frame-link);
|
|
15
|
+
--color-legion-paper: var(--facet-frame-surface);
|
|
16
|
+
--color-legion-line: var(--facet-frame-rule);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@plugin "daisyui" {
|
|
20
|
+
themes:
|
|
21
|
+
night --default,
|
|
22
|
+
winter;
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface SvgViewBox {
|
|
2
|
+
readonly minX: number;
|
|
3
|
+
readonly minY: number;
|
|
4
|
+
readonly width: number;
|
|
5
|
+
readonly height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ViewportSize {
|
|
9
|
+
readonly width: number;
|
|
10
|
+
readonly height: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NativeViewState {
|
|
14
|
+
readonly zoom: number;
|
|
15
|
+
readonly panX?: number;
|
|
16
|
+
readonly panY?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function applySvgViewBox(
|
|
20
|
+
original: SvgViewBox,
|
|
21
|
+
viewport: ViewportSize,
|
|
22
|
+
state: NativeViewState,
|
|
23
|
+
): SvgViewBox {
|
|
24
|
+
const zoom = state.zoom;
|
|
25
|
+
const panX = state.panX ?? 0;
|
|
26
|
+
const panY = state.panY ?? 0;
|
|
27
|
+
return {
|
|
28
|
+
minX: original.minX - (panX * original.width) / viewport.width / zoom,
|
|
29
|
+
minY: original.minY - (panY * original.height) / viewport.height / zoom,
|
|
30
|
+
width: original.width / zoom,
|
|
31
|
+
height: original.height / zoom,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure frame-HTML helpers — document + attribute generation, hostname
|
|
3
|
+
* guard. No DOM mutation; the shell's createArtifactFrame
|
|
4
|
+
* calls these to assemble the iframe configuration.
|
|
5
|
+
*
|
|
6
|
+
* Kept in a separate file from `app.ts` so frame-document invariants — CSP
|
|
7
|
+
* exactness and artifact-byte absence — are testable without the DOM-touching
|
|
8
|
+
* surface.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { galleryDataTheme, type ResolvedGalleryTheme } from "./theme";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Frame element attributes. The shell applies these to every iframe it
|
|
15
|
+
* creates.
|
|
16
|
+
*/
|
|
17
|
+
export interface FrameAttributes {
|
|
18
|
+
readonly referrerpolicy: "no-referrer";
|
|
19
|
+
readonly allow: "";
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly src: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildFrameAttributes(src = "/gallery/frame"): FrameAttributes {
|
|
25
|
+
return {
|
|
26
|
+
referrerpolicy: "no-referrer",
|
|
27
|
+
allow: "",
|
|
28
|
+
title: "facet artifact frame",
|
|
29
|
+
src,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildFrameDocument(options: {
|
|
34
|
+
artifactType: string;
|
|
35
|
+
runtimeUrl: string;
|
|
36
|
+
theme: ResolvedGalleryTheme;
|
|
37
|
+
}): string {
|
|
38
|
+
const { artifactType, runtimeUrl, theme } = options;
|
|
39
|
+
const escapedRuntimeUrl = runtimeUrl
|
|
40
|
+
.replaceAll("&", "&")
|
|
41
|
+
.replaceAll('"', """)
|
|
42
|
+
.replaceAll("<", "<")
|
|
43
|
+
.replaceAll(">", ">");
|
|
44
|
+
return (
|
|
45
|
+
`<!doctype html><html data-theme="${galleryDataTheme(theme)}"><head>` +
|
|
46
|
+
`<meta charset="utf-8"><link rel="stylesheet" href="/gallery/frame/frame.css">` +
|
|
47
|
+
(artifactType === "html" || artifactType === "tsx"
|
|
48
|
+
? `<link rel="stylesheet" href="/gallery/frame/artifact.css">`
|
|
49
|
+
: "") +
|
|
50
|
+
"</head><body>" +
|
|
51
|
+
`<main id="artifact" data-facet-artifact-type="${artifactType}"></main>` +
|
|
52
|
+
`<script type="module" src="${escapedRuntimeUrl}"></script>` +
|
|
53
|
+
"</body></html>"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The shell trusts the loopback only. Anything else is a DNS-rebind or
|
|
59
|
+
* a same-origin-policy escape; the guard fires BEFORE the SSE stream
|
|
60
|
+
* or the iframe is touched.
|
|
61
|
+
*/
|
|
62
|
+
export function isLoopbackHostname(hostname: string): boolean {
|
|
63
|
+
return hostname === "127.0.0.1";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function assertLoopbackHostname(hostname: string): void {
|
|
67
|
+
if (!isLoopbackHostname(hostname)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Refusing to run gallery shell: hostname must be 127.0.0.1 (got: ${JSON.stringify(hostname)})`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>facet gallery</title>
|
|
7
|
+
<link id="facet-favicon" rel="icon" />
|
|
8
|
+
<link rel="stylesheet" href="./styles/tokens.css" />
|
|
9
|
+
<link rel="stylesheet" href="./styles/verdict.css" />
|
|
10
|
+
<link rel="stylesheet" href="./styles/app.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="facet-shell">
|
|
14
|
+
<header id="facet-status">
|
|
15
|
+
<span id="facet-mark" aria-hidden="true">◆</span>
|
|
16
|
+
<span class="title" id="facet-title">facet</span>
|
|
17
|
+
<span class="sep" aria-hidden="true"></span>
|
|
18
|
+
<span id="facet-artifact-title"></span>
|
|
19
|
+
<span class="revision" id="facet-revision">—</span>
|
|
20
|
+
<span class="status-line" id="facet-status-line">idle</span>
|
|
21
|
+
<!--
|
|
22
|
+
Verdict badge. JS wiring: set data-status to the FULL wire
|
|
23
|
+
enum, text to status.split(":")[0], and the tier span to
|
|
24
|
+
`· T${verdict.tier}` (empty while unverified). The badge
|
|
25
|
+
shows read-back results only — the shell never derives one.
|
|
26
|
+
-->
|
|
27
|
+
<span class="facet-verdict facet-verdict--sm" id="facet-verdict" data-status="unverified"
|
|
28
|
+
>unverified<span class="tier"></span
|
|
29
|
+
></span>
|
|
30
|
+
</header>
|
|
31
|
+
<!-- Evidence strip — unhidden when a verdict is loaded. -->
|
|
32
|
+
<section id="facet-evidence" hidden>
|
|
33
|
+
<span class="label">OBSERVED</span>
|
|
34
|
+
<span id="facet-evidence-counts"></span>
|
|
35
|
+
<span class="label">CHANNELS</span>
|
|
36
|
+
<span class="facet-channels" id="facet-evidence-channels"></span>
|
|
37
|
+
<span class="divergence" id="facet-evidence-divergence"></span>
|
|
38
|
+
<span class="binding">bound to <span class="sha" id="facet-evidence-sha"></span></span>
|
|
39
|
+
</section>
|
|
40
|
+
<main id="facet-canvas">
|
|
41
|
+
<div id="facet-swapbar" hidden><div class="bar"></div></div>
|
|
42
|
+
<div id="facet-empty">
|
|
43
|
+
<span class="glyph" aria-hidden="true">◌</span>
|
|
44
|
+
<span class="head">No artifact.</span>
|
|
45
|
+
<span class="sub">Publish one to display it here.</span>
|
|
46
|
+
<span class="cmd"
|
|
47
|
+
><span class="p">$</span> facet publish ./artifact.md
|
|
48
|
+
<span class="p">&&</span> facet open</span
|
|
49
|
+
>
|
|
50
|
+
</div>
|
|
51
|
+
<div id="facet-expired" hidden>
|
|
52
|
+
<span class="glyph" aria-hidden="true">✗</span>
|
|
53
|
+
<span class="head">Session expired.</span>
|
|
54
|
+
<span class="sub">The display lease is no longer valid.</span>
|
|
55
|
+
<span class="cmd"><span class="p">$</span> facet open</span>
|
|
56
|
+
</div>
|
|
57
|
+
<!-- The iframe is created + appended by app.ts. JS wiring:
|
|
58
|
+
hide #facet-empty whenever a frame is mounted. -->
|
|
59
|
+
</main>
|
|
60
|
+
<footer id="facet-controls">
|
|
61
|
+
<span id="facet-live" data-state="idle"
|
|
62
|
+
><span class="dot" aria-hidden="true">●</span
|
|
63
|
+
><span id="facet-live-label">idle</span></span
|
|
64
|
+
>
|
|
65
|
+
<span id="facet-error" role="status"></span>
|
|
66
|
+
<span class="spacer"></span>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
id="facet-panzoom-toggle"
|
|
70
|
+
aria-label="toggle pan and zoom"
|
|
71
|
+
aria-pressed="false"
|
|
72
|
+
>
|
|
73
|
+
pan/zoom
|
|
74
|
+
</button>
|
|
75
|
+
<button type="button" id="facet-zoom-out" aria-label="zoom out">−</button>
|
|
76
|
+
<button type="button" id="facet-zoom-reset" aria-label="reset zoom">100%</button>
|
|
77
|
+
<button type="button" id="facet-zoom-in" aria-label="zoom in">+</button>
|
|
78
|
+
<button type="button" id="facet-theme-toggle" aria-label="theme: system">
|
|
79
|
+
theme: system
|
|
80
|
+
</button>
|
|
81
|
+
<div id="facet-export" class="facet-export">
|
|
82
|
+
<button type="button" id="facet-export-toggle" aria-haspopup="menu" aria-expanded="false">
|
|
83
|
+
export
|
|
84
|
+
</button>
|
|
85
|
+
<div id="facet-export-menu" role="menu" hidden>
|
|
86
|
+
<button type="button" id="facet-export-source" role="menuitem">source</button>
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
id="facet-export-render"
|
|
90
|
+
role="menuitem"
|
|
91
|
+
disabled
|
|
92
|
+
title="no stored render"
|
|
93
|
+
>
|
|
94
|
+
render
|
|
95
|
+
</button>
|
|
96
|
+
<button type="button" id="facet-export-sidecar" role="menuitem">sidecar</button>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<button type="button" id="facet-fullscreen" aria-label="fullscreen">fullscreen</button>
|
|
100
|
+
</footer>
|
|
101
|
+
</div>
|
|
102
|
+
<script type="module" src="./app.ts"></script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session persistence for the gallery shell.
|
|
3
|
+
*
|
|
4
|
+
* The gallery's bootstrap token is single-use: once the shell exchanges
|
|
5
|
+
* it for a lease, the URL fragment is dead. A tab refresh re-submits
|
|
6
|
+
* the consumed token, the service rejects it, and the shell dies.
|
|
7
|
+
*
|
|
8
|
+
* To survive refresh, the shell persists the granted lease to
|
|
9
|
+
* `sessionStorage` (same-origin, per-tab, survives F5, never reaches the
|
|
10
|
+
* URL) AFTER a successful bootstrap exchange. On load the order is:
|
|
11
|
+
* 1. If the URL still carries a fresh `bootstrap=…` token, exchange it.
|
|
12
|
+
* 2. Otherwise, look up the persisted session and validate it against
|
|
13
|
+
* the service (cheap authed source request). A live lease re-renders
|
|
14
|
+
* the artifact exactly as before; an expired or service-restart
|
|
15
|
+
* lease renders the typed "session expired" state.
|
|
16
|
+
*
|
|
17
|
+
* The lease manager is in-memory: a service restart invalidates every
|
|
18
|
+
* outstanding lease. The persisted session therefore reports a TTL
|
|
19
|
+
* window — if the service came back within the lease's nominal expiry
|
|
20
|
+
* the session is still live; otherwise it is honestly expired and the
|
|
21
|
+
* shell tells the user to run `facet open` again.
|
|
22
|
+
*
|
|
23
|
+
* The persisted payload carries the bearer authorization plus the lease
|
|
24
|
+
* id. They are scoped to the same loopback origin and never leave the
|
|
25
|
+
* tab; replacing the tab closes the channel. This matches the existing
|
|
26
|
+
* security rule that leases stay header-only (`X-Gallery-Lease`) and
|
|
27
|
+
* never reach a URL.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { isGalleryThemeMode, type GalleryThemeMode } from "./theme";
|
|
31
|
+
|
|
32
|
+
export const GALLERY_SESSION_STORAGE_KEY = "facet:gallery-session";
|
|
33
|
+
|
|
34
|
+
export interface GallerySession {
|
|
35
|
+
readonly authorization: string;
|
|
36
|
+
readonly artifactId: string;
|
|
37
|
+
readonly revisionSha: string;
|
|
38
|
+
readonly lease: { readonly leaseId: string; readonly expiresAt: number };
|
|
39
|
+
readonly theme: GalleryThemeMode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Minimal `sessionStorage`-shaped surface used by the persistence helpers.
|
|
44
|
+
* Production callers pass `window.sessionStorage`; tests pass an in-memory
|
|
45
|
+
* shim so the helpers stay free of any browser global reference.
|
|
46
|
+
*/
|
|
47
|
+
export interface SessionStorageLike {
|
|
48
|
+
getItem(key: string): string | null;
|
|
49
|
+
setItem(key: string, value: string): void;
|
|
50
|
+
removeItem(key: string): void;
|
|
51
|
+
clear(): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function persistSession(storage: SessionStorageLike, session: GallerySession): void {
|
|
55
|
+
storage.setItem(GALLERY_SESSION_STORAGE_KEY, JSON.stringify(session));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function clearSession(storage: SessionStorageLike): void {
|
|
59
|
+
storage.removeItem(GALLERY_SESSION_STORAGE_KEY);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function readPersistedSession(storage: SessionStorageLike): GallerySession | null {
|
|
63
|
+
const raw = storage.getItem(GALLERY_SESSION_STORAGE_KEY);
|
|
64
|
+
if (raw === null || raw.length === 0) return null;
|
|
65
|
+
let parsed: unknown;
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse(raw);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!isGallerySession(parsed)) return null;
|
|
72
|
+
return {
|
|
73
|
+
authorization: parsed.authorization,
|
|
74
|
+
artifactId: parsed.artifactId,
|
|
75
|
+
revisionSha: parsed.revisionSha,
|
|
76
|
+
lease: parsed.lease,
|
|
77
|
+
theme: isGalleryThemeMode(parsed.theme) ? parsed.theme : "system",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type SessionValidation =
|
|
82
|
+
| { readonly valid: true; readonly session: GallerySession }
|
|
83
|
+
| { readonly valid: false; readonly reason: "expired" | "missing" };
|
|
84
|
+
|
|
85
|
+
export function validatePersistedSession(
|
|
86
|
+
session: GallerySession,
|
|
87
|
+
now: number = Date.now(),
|
|
88
|
+
): SessionValidation {
|
|
89
|
+
if (session.lease.expiresAt <= now) return { valid: false, reason: "expired" };
|
|
90
|
+
return { valid: true, session };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isGallerySession(
|
|
94
|
+
value: unknown,
|
|
95
|
+
): value is Omit<GallerySession, "theme"> & { readonly theme?: unknown } {
|
|
96
|
+
if (value === null || typeof value !== "object") return false;
|
|
97
|
+
const record = value as Record<string, unknown>;
|
|
98
|
+
if (typeof record["authorization"] !== "string") return false;
|
|
99
|
+
if (typeof record["artifactId"] !== "string") return false;
|
|
100
|
+
if (typeof record["revisionSha"] !== "string") return false;
|
|
101
|
+
const lease = record["lease"];
|
|
102
|
+
if (lease === null || typeof lease !== "object") return false;
|
|
103
|
+
const leaseRecord = lease as Record<string, unknown>;
|
|
104
|
+
if (typeof leaseRecord["leaseId"] !== "string") return false;
|
|
105
|
+
if (typeof leaseRecord["expiresAt"] !== "number") return false;
|
|
106
|
+
return true;
|
|
107
|
+
}
|