@legionworks/facet 1.10.0 → 1.10.1
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/dist/gallery/{chunk-48emrxg6.js → chunk-0by8qn4x.js} +15 -0
- package/dist/gallery/frame/artifact.css +3 -0
- package/dist/gallery/frame/chunks/{markdown-fvfncadf.js → markdown-g3asgaw3.js} +8 -0
- package/dist/gallery/frame/frame.css +4 -24
- package/dist/gallery/frame/runtime/chart.js +1 -1
- package/dist/gallery/frame/runtime/html.js +1 -1
- package/dist/gallery/frame/runtime/markdown.js +1 -1
- package/dist/gallery/frame/runtime/mermaid.js +1 -1
- package/dist/gallery/frame/runtime/svg.js +1 -1
- package/dist/gallery/frame/runtime/tsx.js +1 -1
- package/dist/gallery/index.html +1 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/html.md +3 -3
- package/docs/reference/mcp.md +15 -2
- package/docs/reference/tsx.md +3 -1
- package/docs/reference/validation.md +2 -2
- package/package.json +1 -1
- package/skills/facet/SKILL.md +3 -4
- package/src/gallery-web/app.ts +20 -1
- package/src/gallery-web/frame/runtime.ts +7 -1
- package/src/gallery-web/frame/styles/artifact.css +3 -0
- package/src/gallery-web/frame/styles/frame.css +4 -24
- package/src/gallery-web/frame-html.ts +5 -1
- package/src/harness-adapters/mcp/server.ts +2 -2
- package/src/service/dispatcher.ts +12 -0
- package/src/shared/html/style-vocabulary.ts +1 -0
- package/templates/capacity-report.tsx +1 -1
- package/templates/fleet-dashboard.html +1 -1
- package/templates/html-release-ledger.html +1 -1
- package/templates/html-status-report.html +1 -1
- package/templates/incident-console.tsx +1 -1
- package/templates/tsx-interactive-counter.tsx +1 -1
- package/templates/tsx-status-report.tsx +1 -1
|
@@ -40,6 +40,9 @@ function buildFrameAttributes(src = "/gallery/frame") {
|
|
|
40
40
|
src
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
function usesArtifactStylesheet(artifactType) {
|
|
44
|
+
return artifactType === "html" || artifactType === "tsx";
|
|
45
|
+
}
|
|
43
46
|
function isLoopbackHostname(hostname) {
|
|
44
47
|
return hostname === "127.0.0.1";
|
|
45
48
|
}
|
|
@@ -5190,6 +5193,13 @@ function createArtifactFrame(options) {
|
|
|
5190
5193
|
renderResult = handle;
|
|
5191
5194
|
return handle;
|
|
5192
5195
|
},
|
|
5196
|
+
setTheme(theme) {
|
|
5197
|
+
const api = raw.contentWindow?.__facetFrame;
|
|
5198
|
+
if (api === null || api === undefined || typeof api.setTheme !== "function") {
|
|
5199
|
+
throw new Error("frame theme API unavailable");
|
|
5200
|
+
}
|
|
5201
|
+
api.setTheme(theme);
|
|
5202
|
+
},
|
|
5193
5203
|
get renderResult() {
|
|
5194
5204
|
return renderResult;
|
|
5195
5205
|
}
|
|
@@ -5782,6 +5792,11 @@ async function startGallery(runtime = browserGalleryRuntime()) {
|
|
|
5782
5792
|
const canCommit = () => themeGeneration === generation && nextMode === requestedThemeMode;
|
|
5783
5793
|
if (!canCommit())
|
|
5784
5794
|
return;
|
|
5795
|
+
if (usesArtifactStylesheet(source.artifactType)) {
|
|
5796
|
+
current.setTheme(nextResolvedTheme);
|
|
5797
|
+
commitTheme(nextMode, nextResolvedTheme);
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5785
5800
|
const next = createArtifactFrame({
|
|
5786
5801
|
artifactType: source.artifactType,
|
|
5787
5802
|
renderer: source.renderer,
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
function isResolvedGalleryTheme(value) {
|
|
12
12
|
return value === "dark" || value === "light";
|
|
13
13
|
}
|
|
14
|
+
function galleryDataTheme(theme) {
|
|
15
|
+
return theme === "light" ? "winter" : "night";
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
// src/gallery-web/frame/renderer-validation.ts
|
|
16
19
|
function validateRenderer(value) {
|
|
@@ -395,6 +398,11 @@ function installGalleryFrameApi(registry) {
|
|
|
395
398
|
setPointerEventsForAllChildren(container, mode === "panzoom" ? "none" : "");
|
|
396
399
|
};
|
|
397
400
|
const api = {
|
|
401
|
+
setTheme(theme) {
|
|
402
|
+
if (!isResolvedGalleryTheme(theme))
|
|
403
|
+
throw new FacetRenderError("frame theme is invalid", "invalid_request");
|
|
404
|
+
document.documentElement.dataset.theme = galleryDataTheme(theme);
|
|
405
|
+
},
|
|
398
406
|
async render(payload) {
|
|
399
407
|
if (rendered)
|
|
400
408
|
throw new FacetRenderError("frame already rendered", "invalid_request");
|
|
@@ -78,26 +78,7 @@ body {
|
|
|
78
78
|
padding-inline: max(16px, calc((100% - 92ch) / 2));
|
|
79
79
|
}
|
|
80
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
|
-
*/
|
|
81
|
+
/* These selectors match usesArtifactStylesheet in frame-html.ts; CSS cannot import it. */
|
|
101
82
|
#artifact[data-facet-artifact-type="html"],
|
|
102
83
|
#artifact[data-facet-artifact-type="tsx"] {
|
|
103
84
|
display: flex;
|
|
@@ -106,10 +87,9 @@ body {
|
|
|
106
87
|
|
|
107
88
|
#artifact[data-facet-artifact-type="html"] > [data-facet-renderer-root],
|
|
108
89
|
#artifact[data-facet-artifact-type="tsx"] > [data-facet-renderer-root] {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/* A full-height root plus a sibling runtime error makes capture bounds grow on every viewport resize. */
|
|
90
|
+
/* Block flow preserves authored sibling order while keeping the renderer root a flex item of the stage. */
|
|
91
|
+
display: block;
|
|
92
|
+
/* Filling the stage keeps a sibling runtime-error line outside capture bounds as the viewport resizes. */
|
|
113
93
|
flex: 1 0 auto;
|
|
114
94
|
}
|
|
115
95
|
|
|
@@ -7,7 +7,7 @@ import"../chunks/markdown-h7pzv34e.js";
|
|
|
7
7
|
import"../chunks/markdown-zazx9h4n.js";
|
|
8
8
|
import {
|
|
9
9
|
installGalleryFrameApi
|
|
10
|
-
} from "../chunks/markdown-
|
|
10
|
+
} from "../chunks/markdown-g3asgaw3.js";
|
|
11
11
|
import {
|
|
12
12
|
createRendererRegistry
|
|
13
13
|
} from "../chunks/markdown-d1wfbs70.js";
|
package/dist/gallery/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./chunk-2dge4gwb.css"><script type="module" crossorigin src="./chunk-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="./chunk-2dge4gwb.css"><script type="module" crossorigin src="./chunk-0by8qn4x.js"></script></head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="facet-shell">
|
|
14
14
|
<header id="facet-status">
|
package/docs/reference/cli.md
CHANGED
|
@@ -125,7 +125,7 @@ errors and next actions:
|
|
|
125
125
|
| code | next action |
|
|
126
126
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
127
127
|
| `artifact_not_found` | Check the artifact ID and list the project. |
|
|
128
|
-
| `revision_not_found` |
|
|
128
|
+
| `revision_not_found` | Check that the artifact has a revision; omit the SHA for latest or use an existing SHA. |
|
|
129
129
|
| `duplicate_revision` | Reuse `error.details.revisionSha`; no new revision was stored. |
|
|
130
130
|
| `output_unwritable` | Fix the output directory, permissions, or rename target. |
|
|
131
131
|
| `evidence_unavailable` | Run Tier 1 again or inspect retention and evidence paths. |
|
package/docs/reference/html.md
CHANGED
|
@@ -142,15 +142,15 @@ an included Tailwind utility outside this list can still render.
|
|
|
142
142
|
|
|
143
143
|
### Recommended Tailwind utilities
|
|
144
144
|
|
|
145
|
-
`block`, `flex`, `grid`, `inline-flex`, `flex-col`, `flex-wrap`, `items-center`, `justify-between`, `justify-center`, `grid-cols-1`, `grid-cols-2`, `grid-cols-3`, `gap-2`, `gap-3`, `gap-4`, `gap-6`, `p-2`, `p-3`, `p-4`, `p-6`, `px-3`, `px-4`, `py-2`, `py-3`, `m-0`, `mt-2`, `mt-4`, `mb-2`, `mb-4`, `w-full`, `max-w-prose`, `max-w-2xl`, `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, `font-medium`, `font-semibold`, `font-bold`, `leading-relaxed`, `text-left`, `text-center`, `text-right`, `text-legion-ink`, `text-legion-muted`, `text-legion-cyan`, `bg-legion-paper`, `bg-legion-ink`, `bg-legion-cyan`, `border`, `border-2`, `border-legion-line`, `rounded`, `rounded-box`, `overflow-x-auto`, `table`, `table-zebra`.
|
|
145
|
+
`block`, `flex`, `grid`, `inline-flex`, `flex-col`, `flex-wrap`, `items-center`, `justify-between`, `justify-center`, `grid-cols-1`, `grid-cols-2`, `grid-cols-3`, `gap-2`, `gap-3`, `gap-4`, `gap-6`, `p-2`, `p-3`, `p-4`, `p-6`, `px-3`, `px-4`, `py-2`, `py-3`, `m-0`, `mt-2`, `mt-4`, `mb-2`, `mb-4`, `mx-auto`, `w-full`, `max-w-prose`, `max-w-2xl`, `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, `font-medium`, `font-semibold`, `font-bold`, `leading-relaxed`, `text-left`, `text-center`, `text-right`, `text-legion-ink`, `text-legion-muted`, `text-legion-cyan`, `bg-legion-paper`, `bg-legion-ink`, `bg-legion-cyan`, `border`, `border-2`, `border-legion-line`, `rounded`, `rounded-box`, `overflow-x-auto`, `table`, `table-zebra`.
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
60 recommended utilities.
|
|
148
148
|
|
|
149
149
|
### Recommended daisyUI components
|
|
150
150
|
|
|
151
151
|
`alert`, `badge`, `btn`, `card`, `stat`, `table`.
|
|
152
152
|
|
|
153
|
-
6 recommended components ·
|
|
153
|
+
6 recommended components · 65 documented recommendations.
|
|
154
154
|
|
|
155
155
|
<!-- VOCABULARY:END -->
|
|
156
156
|
|
package/docs/reference/mcp.md
CHANGED
|
@@ -11,9 +11,14 @@ bun add -g @legionworks/facet
|
|
|
11
11
|
facet-mcp
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Releases through `1.9.0` ship a `facet-mcp` bin that cannot launch
|
|
14
|
+
Releases through `1.9.0` ship a `facet-mcp` bin that cannot launch: its entry file has no `#!/usr/bin/env bun` line. Use `1.10.0` or newer. With `1.10.0` on Bun `1.4.0`, each of these answered JSON-RPC `initialize` and listed six tools:
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
- `bun add -g @legionworks/facet`, then `facet-mcp`
|
|
17
|
+
- `npm i -g @legionworks/facet`, then `facet-mcp`
|
|
18
|
+
- `npx -p @legionworks/facet facet-mcp`
|
|
19
|
+
- `bunx -p @legionworks/facet facet-mcp`
|
|
20
|
+
|
|
21
|
+
Run `npx -p` from outside a Facet checkout. Inside one, npx resolves the local project instead of the published package and fails with `facet-mcp: command not found`.
|
|
17
22
|
|
|
18
23
|
The adapter resolves the CLI in this order: `FACET_CLI`, then `bun <adapter-relative-repository>/src/cli/main.ts`, then `facet` on `PATH`. Set `FACET_CLI` to an absolute CLI executable when the adapter should use another installation.
|
|
19
24
|
|
|
@@ -69,6 +74,14 @@ Set `FACET_HOME` in the host configuration when the adapter must use a non-defau
|
|
|
69
74
|
|
|
70
75
|
Exactly one of `sourceText` or `file` is required. The adapter returns `invalid_request` when both or neither are supplied.
|
|
71
76
|
|
|
77
|
+
Tier 0 means structure was parsed and compiled, not rendered. Request
|
|
78
|
+
`facet_read_back` with `tier: "visual"` before reporting a render. Visual
|
|
79
|
+
evidence covers the first rendered state of an interactive TSX artifact;
|
|
80
|
+
later interaction is not validated and does not change the stored verdict.
|
|
81
|
+
When testing by hand with the MCP Inspector CLI, `--tool-arg key=value` passes
|
|
82
|
+
`\n` literally, so multi-line `sourceText` arrives as one line; pass `file`
|
|
83
|
+
instead.
|
|
84
|
+
|
|
72
85
|
The MCP surface has six artifact tools. Start a cold-home flow with `facet_create`, then call `facet_publish`, `facet_read_back`, and `facet_export`. Each tool advertises its input schema in `tools/list`; run `facet doctor` through the CLI.
|
|
73
86
|
|
|
74
87
|
## Result and error handling
|
package/docs/reference/tsx.md
CHANGED
|
@@ -74,7 +74,9 @@ has no host port or service capability.
|
|
|
74
74
|
The TSX verifier loads the vendored `artifact.css` used by the gallery frame.
|
|
75
75
|
A runtime error after initial render, such as one thrown by a click handler,
|
|
76
76
|
appears as a gallery signal in an `aria-live="polite"` status region. It does
|
|
77
|
-
not rewrite the immutable verdict
|
|
77
|
+
not rewrite the immutable verdict. It clears on revision swap. Switching the
|
|
78
|
+
theme keeps the frame mounted, including interactive component state and any
|
|
79
|
+
runtime error signal already displayed.
|
|
78
80
|
|
|
79
81
|
Revision source remains immutable. Compilation creates derived bytes recorded at
|
|
80
82
|
the run's `compiled_path`; TSX source export writes the original `.tsx` bytes.
|
|
@@ -80,8 +80,8 @@ capture it emulates `prefers-reduced-motion: reduce` and awaits
|
|
|
80
80
|
Tier 1 uses the same artifact-type layout rules as the gallery frame: Mermaid,
|
|
81
81
|
SVG, and chart roots are safely centered on both axes, with oversized content
|
|
82
82
|
remaining reachable by scrolling; Markdown is top-aligned in a horizontally
|
|
83
|
-
centered column capped at about 92ch; HTML and TSX
|
|
84
|
-
|
|
83
|
+
centered column capped at about 92ch; HTML and TSX use top-aligned document
|
|
84
|
+
flow, where a fixed-width layout can center itself with `mx-auto`. The renderer-parity gate
|
|
85
85
|
compares both the gallery and verifier renderer-module sets and CSS/stylesheet
|
|
86
86
|
sets.
|
|
87
87
|
|
package/package.json
CHANGED
package/skills/facet/SKILL.md
CHANGED
|
@@ -93,7 +93,7 @@ non-whitespace text; the verdict requires a screenshot.
|
|
|
93
93
|
| ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
|
94
94
|
| `invalid_request` | Correct the command flags, artifact type, or source bytes, then retry. |
|
|
95
95
|
| `artifact_not_found` | List or create the artifact, then use its returned ID. |
|
|
96
|
-
| `revision_not_found` |
|
|
96
|
+
| `revision_not_found` | Check that the artifact has a revision; omit the SHA for latest, or replace it with a returned revision SHA. |
|
|
97
97
|
| `duplicate_revision` | Use `error.details.revisionSha` as the existing revision SHA; do not republish identical bytes. |
|
|
98
98
|
| `evidence_unavailable` | Request Tier 1 evidence for that revision before a render export, or export source instead. |
|
|
99
99
|
| `output_unwritable` | Choose a writable `--out` path, resolve collisions, or use `--force` deliberately. |
|
|
@@ -114,9 +114,8 @@ non-whitespace text; the verdict requires a screenshot.
|
|
|
114
114
|
- With shell access, the CLI is the integration — MCP is for structured-tool-only hosts, even when the host owns an MCP client. On that surface, `facet_open_url` is safe because it always uses `--no-launch`.
|
|
115
115
|
- For MCP registration, use `bun add -g @legionworks/facet` and the bare
|
|
116
116
|
`facet-mcp` command, or the no-install fallback `npx -p @legionworks/facet
|
|
117
|
-
facet-mcp
|
|
118
|
-
|
|
119
|
-
that as an environment-specific caveat, not a universal Bun claim.
|
|
117
|
+
facet-mcp` (`bunx -p` also works). Use `1.10.0` or newer; earlier releases
|
|
118
|
+
ship a `facet-mcp` bin that cannot launch.
|
|
120
119
|
|
|
121
120
|
## Deeper reference
|
|
122
121
|
|
package/src/gallery-web/app.ts
CHANGED
|
@@ -23,7 +23,12 @@
|
|
|
23
23
|
* surfaces an error badge.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
assertLoopbackHostname,
|
|
28
|
+
buildFrameAttributes,
|
|
29
|
+
usesArtifactStylesheet,
|
|
30
|
+
type FrameAttributes,
|
|
31
|
+
} from "./frame-html";
|
|
27
32
|
import { planSwap, type SwapPlanStep } from "./swap";
|
|
28
33
|
import { connectRevisionStream } from "./sse-client";
|
|
29
34
|
import type { VerdictObserved } from "../shared/contracts/validation";
|
|
@@ -376,6 +381,7 @@ export interface CreatedArtifactFrame {
|
|
|
376
381
|
payload: FrameRenderPayload,
|
|
377
382
|
timeoutMs: number,
|
|
378
383
|
) => Promise<FrameRenderResultHandle>;
|
|
384
|
+
readonly setTheme: (theme: ResolvedGalleryTheme) => void;
|
|
379
385
|
/** Handle from the last successful render (null before the first). */
|
|
380
386
|
readonly renderResult: FrameRenderResultHandle | null;
|
|
381
387
|
}
|
|
@@ -488,6 +494,14 @@ export function createArtifactFrame(options: CreateArtifactFrameOptions): Create
|
|
|
488
494
|
renderResult = handle;
|
|
489
495
|
return handle;
|
|
490
496
|
},
|
|
497
|
+
setTheme(theme) {
|
|
498
|
+
// oxlint-disable-next-line no-underscore-dangle
|
|
499
|
+
const api = raw.contentWindow?.__facetFrame;
|
|
500
|
+
if (api === null || api === undefined || typeof api.setTheme !== "function") {
|
|
501
|
+
throw new Error("frame theme API unavailable");
|
|
502
|
+
}
|
|
503
|
+
api.setTheme(theme);
|
|
504
|
+
},
|
|
491
505
|
get renderResult(): FrameRenderResultHandle | null {
|
|
492
506
|
return renderResult;
|
|
493
507
|
},
|
|
@@ -1309,6 +1323,11 @@ export async function startGallery(runtime = browserGalleryRuntime()): Promise<v
|
|
|
1309
1323
|
const canCommit = (): boolean =>
|
|
1310
1324
|
themeGeneration === generation && nextMode === requestedThemeMode;
|
|
1311
1325
|
if (!canCommit()) return;
|
|
1326
|
+
if (usesArtifactStylesheet(source.artifactType)) {
|
|
1327
|
+
current.setTheme(nextResolvedTheme);
|
|
1328
|
+
commitTheme(nextMode, nextResolvedTheme);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1312
1331
|
const next = createArtifactFrame({
|
|
1313
1332
|
artifactType: source.artifactType,
|
|
1314
1333
|
renderer: source.renderer,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isTsxExecutionMode, type TsxExecutionMode } from "../../shared/tsx/execution";
|
|
2
|
-
import { isResolvedGalleryTheme } from "../theme";
|
|
2
|
+
import { galleryDataTheme, isResolvedGalleryTheme, type ResolvedGalleryTheme } from "../theme";
|
|
3
3
|
|
|
4
4
|
import { validateRenderer } from "./renderer-validation";
|
|
5
5
|
import {
|
|
@@ -80,6 +80,7 @@ interface DiagramRegionGestures {
|
|
|
80
80
|
|
|
81
81
|
export interface GalleryFrameApi {
|
|
82
82
|
render(payload: FrameRenderPayload): Promise<RenderResult>;
|
|
83
|
+
setTheme(theme: ResolvedGalleryTheme): void;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
declare global {
|
|
@@ -410,6 +411,11 @@ export function installGalleryFrameApi(registry: RendererRegistry): void {
|
|
|
410
411
|
};
|
|
411
412
|
|
|
412
413
|
const api: GalleryFrameApi = {
|
|
414
|
+
setTheme(theme) {
|
|
415
|
+
if (!isResolvedGalleryTheme(theme))
|
|
416
|
+
throw new FacetRenderError("frame theme is invalid", "invalid_request");
|
|
417
|
+
document.documentElement.dataset.theme = galleryDataTheme(theme);
|
|
418
|
+
},
|
|
413
419
|
async render(payload: FrameRenderPayload): Promise<RenderResult> {
|
|
414
420
|
if (rendered) throw new FacetRenderError("frame already rendered", "invalid_request");
|
|
415
421
|
const validated = validatePayload(payload);
|
|
@@ -78,26 +78,7 @@ body {
|
|
|
78
78
|
padding-inline: max(16px, calc((100% - 92ch) / 2));
|
|
79
79
|
}
|
|
80
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
|
-
*/
|
|
81
|
+
/* These selectors match usesArtifactStylesheet in frame-html.ts; CSS cannot import it. */
|
|
101
82
|
#artifact[data-facet-artifact-type="html"],
|
|
102
83
|
#artifact[data-facet-artifact-type="tsx"] {
|
|
103
84
|
display: flex;
|
|
@@ -106,10 +87,9 @@ body {
|
|
|
106
87
|
|
|
107
88
|
#artifact[data-facet-artifact-type="html"] > [data-facet-renderer-root],
|
|
108
89
|
#artifact[data-facet-artifact-type="tsx"] > [data-facet-renderer-root] {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/* A full-height root plus a sibling runtime error makes capture bounds grow on every viewport resize. */
|
|
90
|
+
/* Block flow preserves authored sibling order while keeping the renderer root a flex item of the stage. */
|
|
91
|
+
display: block;
|
|
92
|
+
/* Filling the stage keeps a sibling runtime-error line outside capture bounds as the viewport resizes. */
|
|
113
93
|
flex: 1 0 auto;
|
|
114
94
|
}
|
|
115
95
|
|
|
@@ -31,6 +31,10 @@ export function buildFrameAttributes(src = "/gallery/frame"): FrameAttributes {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export function usesArtifactStylesheet(artifactType: string): boolean {
|
|
35
|
+
return artifactType === "html" || artifactType === "tsx";
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
export function buildFrameDocument(options: {
|
|
35
39
|
artifactType: string;
|
|
36
40
|
runtimeUrl: string;
|
|
@@ -45,7 +49,7 @@ export function buildFrameDocument(options: {
|
|
|
45
49
|
return (
|
|
46
50
|
`<!doctype html><html data-theme="${galleryDataTheme(theme)}"><head>` +
|
|
47
51
|
`<meta charset="utf-8"><link rel="stylesheet" href="/gallery/frame/frame.css">` +
|
|
48
|
-
(artifactType
|
|
52
|
+
(usesArtifactStylesheet(artifactType)
|
|
49
53
|
? `<link rel="stylesheet" href="/gallery/frame/artifact.css">`
|
|
50
54
|
: "") +
|
|
51
55
|
"</head><body>" +
|
|
@@ -145,7 +145,7 @@ export function createFacetMcpServer(): Server {
|
|
|
145
145
|
),
|
|
146
146
|
facet_publish: defineTool(
|
|
147
147
|
PublishToolSchema,
|
|
148
|
-
|
|
148
|
+
'Publish exactly one sourceText or local file. Check envelope.ok separately from data.verdict.status: a stored verdict status of error is not a transport failure. Tier 0 parses and compiles structure but is not rendered; call facet_read_back with tier: "visual" before reporting a render.',
|
|
149
149
|
async (input) => {
|
|
150
150
|
const source = publishSource(input);
|
|
151
151
|
return invoke(buildFacetArgs("publish", { ...input, ...source }), source);
|
|
@@ -153,7 +153,7 @@ export function createFacetMcpServer(): Server {
|
|
|
153
153
|
),
|
|
154
154
|
facet_read_back: defineTool(
|
|
155
155
|
ReadBackToolSchema,
|
|
156
|
-
"Read back
|
|
156
|
+
"Read back a stored verdict at Tier 0, Tier 1, or visual. Tier 1 and visual need browser evidence from the pinned browser; inspect envelope.ok before verdict status. Visual covers the first rendered state; later interaction is not validated and does not change the stored verdict.",
|
|
157
157
|
async (input) => invoke(buildFacetArgs("read_back", input)),
|
|
158
158
|
),
|
|
159
159
|
facet_status: defineTool(
|
|
@@ -526,6 +526,12 @@ export async function dispatch(
|
|
|
526
526
|
? deps.repository.getLatestRevision(command.artifactId)
|
|
527
527
|
: deps.repository.getRevisionBySha(command.artifactId, command.revisionSha);
|
|
528
528
|
if (revision === null) {
|
|
529
|
+
if (deps.repository.getArtifactById(command.artifactId) === null) {
|
|
530
|
+
throw new FacetError("artifact_not_found", "Artifact not found", {
|
|
531
|
+
retryable: false,
|
|
532
|
+
details: { artifactId: command.artifactId },
|
|
533
|
+
});
|
|
534
|
+
}
|
|
529
535
|
throw new FacetError("revision_not_found", "Revision not found", {
|
|
530
536
|
retryable: false,
|
|
531
537
|
details: {
|
|
@@ -614,6 +620,12 @@ export async function dispatch(
|
|
|
614
620
|
? deps.repository.getLatestRevision(command.artifactId)
|
|
615
621
|
: deps.repository.getRevisionBySha(command.artifactId, command.revisionSha);
|
|
616
622
|
if (revision === null) {
|
|
623
|
+
if (deps.repository.getArtifactById(command.artifactId) === null) {
|
|
624
|
+
throw new FacetError("artifact_not_found", "Artifact not found", {
|
|
625
|
+
retryable: false,
|
|
626
|
+
details: { artifactId: command.artifactId },
|
|
627
|
+
});
|
|
628
|
+
}
|
|
617
629
|
throw new FacetError("revision_not_found", "Revision not found", {
|
|
618
630
|
retryable: false,
|
|
619
631
|
details: {
|
|
@@ -16,7 +16,7 @@ export default function CapacityReport() {
|
|
|
16
16
|
const capacityPercent = Math.round((readyWorkers / workerCapacity) * 100);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<main className="max-w-2xl p-6 gap-6 flex flex-col bg-legion-paper text-legion-ink">
|
|
19
|
+
<main className="max-w-2xl mx-auto p-6 gap-6 flex flex-col bg-legion-paper text-legion-ink">
|
|
20
20
|
<header className="flex flex-col gap-2">
|
|
21
21
|
<div className="flex flex-wrap gap-2">
|
|
22
22
|
<span className="badge">capacity report</span>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<title>Fleet dashboard</title>
|
|
5
5
|
</head>
|
|
6
6
|
<body class="bg-legion-paper text-legion-ink leading-relaxed">
|
|
7
|
-
<main class="max-w-2xl p-6 gap-6 flex flex-col">
|
|
7
|
+
<main class="max-w-2xl mx-auto p-6 gap-6 flex flex-col">
|
|
8
8
|
<header class="flex flex-col gap-2">
|
|
9
9
|
<div class="flex flex-wrap gap-2 items-center">
|
|
10
10
|
<span class="badge">operations</span>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<title>Release ledger</title>
|
|
5
5
|
</head>
|
|
6
6
|
<body class="bg-legion-paper text-legion-ink leading-relaxed">
|
|
7
|
-
<main class="max-w-2xl p-6 gap-6 flex flex-col">
|
|
7
|
+
<main class="max-w-2xl mx-auto p-6 gap-6 flex flex-col">
|
|
8
8
|
<header class="flex flex-col gap-2">
|
|
9
9
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
|
10
10
|
<span class="badge">release ledger</span>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<title>Status report</title>
|
|
21
21
|
</head>
|
|
22
22
|
<body class="bg-legion-paper text-legion-ink leading-relaxed">
|
|
23
|
-
<main class="max-w-2xl p-6 gap-4 flex flex-col">
|
|
23
|
+
<main class="max-w-2xl mx-auto p-6 gap-4 flex flex-col">
|
|
24
24
|
<header class="flex flex-col gap-2">
|
|
25
25
|
<h1 class="text-2xl font-bold">Status report</h1>
|
|
26
26
|
<p class="text-legion-muted text-sm">
|
|
@@ -44,7 +44,7 @@ export default function IncidentConsole() {
|
|
|
44
44
|
const criticalCount = incidents.filter((incident) => incident.severity === "critical").length;
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
|
-
<main className="max-w-2xl p-6 gap-6 flex flex-col bg-legion-paper text-legion-ink">
|
|
47
|
+
<main className="max-w-2xl mx-auto p-6 gap-6 flex flex-col bg-legion-paper text-legion-ink">
|
|
48
48
|
<header className="flex flex-col gap-2">
|
|
49
49
|
<div className="flex flex-wrap gap-2 items-center">
|
|
50
50
|
<span className="badge">incident console</span>
|
|
@@ -4,7 +4,7 @@ export default function InteractiveCounter() {
|
|
|
4
4
|
const [count, setCount] = useState(0);
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
|
-
<section className="card bg-legion-paper p-6 w-full max-w-prose">
|
|
7
|
+
<section className="card bg-legion-paper p-6 w-full max-w-prose mx-auto">
|
|
8
8
|
<h1 className="text-2xl font-semibold text-legion-ink">Interactive counter</h1>
|
|
9
9
|
<p className="text-legion-muted mt-2">Button presses: {count}</p>
|
|
10
10
|
<button
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
export default function StatusReport() {
|
|
4
4
|
return (
|
|
5
|
-
<section className="card bg-legion-paper p-6 w-full max-w-prose">
|
|
5
|
+
<section className="card bg-legion-paper p-6 w-full max-w-prose mx-auto">
|
|
6
6
|
<h1 className="text-2xl font-semibold text-legion-ink">Status report</h1>
|
|
7
7
|
<p className="text-legion-muted mt-2">Verification state for the current release.</p>
|
|
8
8
|
<section className="mt-4">
|