@volter/editor-react 0.5.211 → 0.5.212
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/contributions/component-contracts.service.ts +1 -1
- package/dist-node/serving.mjs +14770 -1629
- package/package.json +20 -9
- package/serving/component-prop-worker.ts +3 -19
- package/serving/index.ts +20 -8
- package/serving/source-diagnostics.ts +311 -0
- package/serving/ui-oid-plugin.ts +154 -775
- package/src/source/component-prop-types.ts +6 -62
- package/src/source/drei-detailed-names.ts +24 -0
- package/src/source/oid-transform.ts +166 -376
- package/src/source/plan-csf-story.ts +3 -26
- package/src/source/plan-extract-component.ts +15 -60
- package/src/source/plan-fork-component.ts +25 -92
- package/src/source/plan-named-style.ts +3 -17
- package/src/source/plan-source-edit.ts +9 -40
- package/src/source/r3f-contract-resolver.ts +9 -103
- package/src/source/r3f-physics-binding.ts +28 -186
- package/src/source/r3f-project-contracts.ts +13 -103
- package/src/source/relative-import-specifier.ts +3 -18
- package/src/source/reparent-guard.ts +16 -61
- package/src/source/source-edit-request.ts +2 -20
- package/src/source/ts-ast.ts +5 -95
- package/src/source/utility-class-support.ts +4 -32
- package/src/source/writer.ts +74 -326
- package/src/source/r3f-diagnostic-index.ts +0 -165
- package/src/source/r3f-environment-binding.ts +0 -106
- package/src/source/r3f-joint-binding.ts +0 -228
- package/src/source/r3f-lod-binding.ts +0 -81
- package/src/source/r3f-particle-binding.ts +0 -213
- package/src/source/syntactic-prop-specs.ts +0 -296
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* (owner-directed overlap audit, 2026-08-22): `R3fSourceAuthoringAdapter.
|
|
5
|
-
* relativeModuleSpecifier`, `ReactRootAuthoringAdapter.relativeProjectModule`,
|
|
6
|
-
* `pixi-source-write-target`'s `relativeProjectModule`, and the
|
|
7
|
-
* `relativeSpecifier` locals in `plan-fork-component.ts` and
|
|
8
|
-
* `plan-extract-component.ts`.
|
|
9
|
-
*
|
|
10
|
-
* Dependency-free on purpose (no `node:path`): two of the five consumers are
|
|
11
|
-
* pure planner modules that must stay headlessly probeable, and a specifier is
|
|
12
|
-
* a POSIX-slash artifact regardless of host platform.
|
|
13
|
-
*
|
|
14
|
-
* What stays at the call site, deliberately: resolving the two paths into the
|
|
15
|
-
* SAME address space (project-relative vs absolute — both work, the walk eats
|
|
16
|
-
* any shared prefix, but they must match). A source extension on the target is
|
|
17
|
-
* stripped; an extension-less module id passes through unchanged.
|
|
18
|
-
*/
|
|
1
|
+
/** Construct a relative import specifier from two paths in the same address
|
|
2
|
+
* space, using POSIX slashes and stripping the target source extension. No
|
|
3
|
+
* platform path API is needed, so browser and Node planners share this walk. */
|
|
19
4
|
export function relativeImportSpecifier(fromFile: string, toModule: string): string {
|
|
20
5
|
const from = normalize(fromFile).split('/').filter(Boolean).slice(0, -1);
|
|
21
6
|
const to = normalize(toModule)
|
|
@@ -1,36 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Why here: `plan-source-edit.ts` is the ONE place both tiers (dev server and hosted
|
|
7
|
-
* browser) route a source edit through, and its `reparent` case used to be a bare
|
|
8
|
-
* `reparentElement` — a textual line-block move that is sound only between two parents
|
|
9
|
-
* in the same lexical scope with identical accumulated transforms. The moment the
|
|
10
|
-
* hierarchy invites arbitrary drags it is unsound three ways, all of them SILENT:
|
|
11
|
-
*
|
|
12
|
-
* 1. Lexical capture — an element authored inside a component function may reference
|
|
13
|
-
* that function's own bindings (`color={tint}`, a `ref`, a mapped variable).
|
|
14
|
-
* Textually moving it elsewhere produces code that does not compile, or compiles
|
|
15
|
-
* against a DIFFERENT binding with the same name.
|
|
16
|
-
* 2. Transform re-basing — a verbatim move preserves the authored `position` tuple,
|
|
17
|
-
* not the world position. Between identity parents those are the same thing;
|
|
18
|
-
* between transformed parents the object teleports on the next mount.
|
|
19
|
-
* 3. Boundary blindness — a self-closing destination (which is what nearly every R3F
|
|
20
|
-
* component callsite is) made `reparentElement` insert a PRECEDING SIBLING instead
|
|
21
|
-
* of a child, and a destination rendered N times would author the element once and
|
|
22
|
-
* render it N times.
|
|
23
|
-
*
|
|
24
|
-
* The response to all three is the same and is the anti-shim rule: REFUSE, naming the
|
|
25
|
-
* thing that made the move unsafe. This module never rewrites an expression, never
|
|
26
|
-
* renames a binding, and never writes a computed value over an authored one — the
|
|
27
|
-
* literal-vs-dynamic guard is as sacred here as at the gizmo.
|
|
28
|
-
*
|
|
29
|
-
* R4 (one transaction) falls out of living here: every rule is decided before the first
|
|
30
|
-
* edit, a refusal returns the input string identity-equal, and an accepted move returns
|
|
31
|
-
* ONE new whole-file string (tuple rewrites + slot opening + the move), which the
|
|
32
|
-
* caller's existing whole-file snapshot inverse turns into a single undo step.
|
|
33
|
-
*/
|
|
1
|
+
/** Plan a JSX reparent after checking lexical capture, world-transform
|
|
2
|
+
* preservation, and destination legality. Unsupported moves return the input
|
|
3
|
+
* unchanged with a named refusal; accepted moves return one complete next-file
|
|
4
|
+
* source string for one history transaction. */
|
|
34
5
|
|
|
35
6
|
import ts from 'typescript';
|
|
36
7
|
import { parseAuthoringTsx } from './ts-ast';
|
|
@@ -53,12 +24,8 @@ const EPS = 1e-6;
|
|
|
53
24
|
|
|
54
25
|
export interface ReparentOptions {
|
|
55
26
|
readonly rebase?: ReparentRebase | undefined;
|
|
56
|
-
/**
|
|
57
|
-
*
|
|
58
|
-
* destination is authored once and rendered N times (a `.map`ped callsite, or a
|
|
59
|
-
* component definition with N instances), so the moved element would be authored
|
|
60
|
-
* once and rendered N times too — R3 refuses that with the count.
|
|
61
|
-
*/
|
|
27
|
+
/** Number of live objects sharing the destination OID. Multiple instances
|
|
28
|
+
* would repeat the moved child, so the planner refuses that destination. */
|
|
62
29
|
readonly destinationInstances?: number | undefined;
|
|
63
30
|
/**
|
|
64
31
|
* The same count for the element being MOVED. More than one is the mirrored case and
|
|
@@ -72,7 +39,7 @@ export interface ReparentPlan extends StructEditResult {
|
|
|
72
39
|
/** The named reason the move was refused. Present iff `changed` is false and the
|
|
73
40
|
* input was left byte-identical because a rule said no. */
|
|
74
41
|
readonly error?: string;
|
|
75
|
-
/** Applied
|
|
42
|
+
/** Applied with a warning for the author. */
|
|
76
43
|
readonly warning?: string;
|
|
77
44
|
}
|
|
78
45
|
|
|
@@ -115,9 +82,7 @@ function contains(outer: ts.Node, sf: ts.SourceFile, offset: number): boolean {
|
|
|
115
82
|
return outer.getStart(sf) <= offset && offset < outer.getEnd();
|
|
116
83
|
}
|
|
117
84
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
/** Every name a declaration binds, flattened through destructuring patterns. */
|
|
85
|
+
// --- Scope safety ---
|
|
121
86
|
function boundNames(name: ts.BindingName, into: Set<string>): void {
|
|
122
87
|
if (ts.isIdentifier(name)) {
|
|
123
88
|
into.add(name.text);
|
|
@@ -227,19 +192,9 @@ function lineOf(node: ts.Node, sf: ts.SourceFile): number {
|
|
|
227
192
|
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
228
193
|
}
|
|
229
194
|
|
|
230
|
-
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* It counts an identifier it is unsure about, so an unrecognized shape refuses rather
|
|
235
|
-
* than moves. The one deliberate subtraction is names the subtree binds FOR ITSELF —
|
|
236
|
-
* without it, a `.map((angle) => …)` inside the moved element would read as a capture
|
|
237
|
-
* of the outer scope and nothing containing a mapped list could ever move. Stated
|
|
238
|
-
* plainly, because it is not free: a name bound inside the subtree in one place and
|
|
239
|
-
* captured from outside it in another is subtracted too, and that capture is missed.
|
|
240
|
-
* A tighter answer needs a real binder, not a name set; this is the honest limit of
|
|
241
|
-
* the cheap one.
|
|
242
|
-
*/
|
|
195
|
+
/** Find names referenced outside the subtree. Names bound anywhere inside
|
|
196
|
+
* it are subtracted; the name-set walk does not resolve shadowed captures.
|
|
197
|
+
* Unrecognized reference shapes count conservatively as captures. */
|
|
243
198
|
export function referencedIdentifiers(node: JsxElementNode): Map<string, ts.Node> {
|
|
244
199
|
const referenced = new Map<string, ts.Node>();
|
|
245
200
|
const localBindings = new Set<string>();
|
|
@@ -295,7 +250,7 @@ function leftmostIdentifier(node: ts.PropertyAccessExpression): ts.Node {
|
|
|
295
250
|
return cursor;
|
|
296
251
|
}
|
|
297
252
|
|
|
298
|
-
//
|
|
253
|
+
// --- Destination legality ---
|
|
299
254
|
|
|
300
255
|
type ChildrenSlot = 'paired' | 'expandable';
|
|
301
256
|
|
|
@@ -409,7 +364,7 @@ export function planReparent(
|
|
|
409
364
|
return refuse(`<${destinationTag}> is inside <${elementTag}> — a move there would be a cycle.`);
|
|
410
365
|
}
|
|
411
366
|
|
|
412
|
-
// ---
|
|
367
|
+
// --- Destination legality ---
|
|
413
368
|
const destinationInstances = options.destinationInstances;
|
|
414
369
|
if (destinationInstances !== undefined && destinationInstances > 1) {
|
|
415
370
|
return refuse(
|
|
@@ -433,7 +388,7 @@ export function planReparent(
|
|
|
433
388
|
}
|
|
434
389
|
}
|
|
435
390
|
|
|
436
|
-
//
|
|
391
|
+
// Keep the destination within the element's lexical scope.
|
|
437
392
|
for (const [name, reference] of referencedIdentifiers(element)) {
|
|
438
393
|
const scope = bindingScopeOf(name, reference);
|
|
439
394
|
if (!scope || contains(scope, sf, parentStart)) continue;
|
|
@@ -443,7 +398,7 @@ export function planReparent(
|
|
|
443
398
|
);
|
|
444
399
|
}
|
|
445
400
|
|
|
446
|
-
//
|
|
401
|
+
// Preserve the world transform across the new parent.
|
|
447
402
|
const rebase = options.rebase;
|
|
448
403
|
const tagEnd = findTagEnd(code, elementStart);
|
|
449
404
|
if (tagEnd < 0) return refuse(`<${elementTag}>'s opening tag could not be read.`);
|
|
@@ -476,7 +431,7 @@ export function planReparent(
|
|
|
476
431
|
});
|
|
477
432
|
}
|
|
478
433
|
|
|
479
|
-
//
|
|
434
|
+
// All checks passed; apply tuple rewrites and the element move.
|
|
480
435
|
let next = code;
|
|
481
436
|
for (const { channel, values, scalar } of pending) {
|
|
482
437
|
const uniform =
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* deliberately its own file.
|
|
4
|
-
*
|
|
5
|
-
* It sits between two programs that must NOT share a type graph: the browser
|
|
6
|
-
* editor (`ui-source/source-write-backend.ts`, which posts it) and the NODE
|
|
7
|
-
* dev server (`vite-plugin-ui-oid.ts`'s middleware and `ui-source/plan-source-edit.ts`,
|
|
8
|
-
* which plan and apply it). Until 2026-09-20 both sides imported the type from
|
|
9
|
-
* `source-write-backend.ts` — and that module value-imports `files/project-files.ts`,
|
|
10
|
-
* which reaches `storage/` and from there the editor SHELL STORE, so
|
|
11
|
-
* `tsconfig.server.json` compiled 231 browser modules including 36 React ones to
|
|
12
|
-
* typecheck a Node program (the fourth blind review's "the Node server's type graph
|
|
13
|
-
* now reaching the browser app"; the config's own header recorded the 310 errors
|
|
14
|
-
* that forced its `paths` and `jsx` settings).
|
|
15
|
-
*
|
|
16
|
-
* So: the REQUEST lives here, importing nothing but its two sibling AST types, and
|
|
17
|
-
* `source-write-backend.ts` re-exports it for the browser callers that already name
|
|
18
|
-
* it. Anything added to this file must stay free of browser and Node imports alike —
|
|
19
|
-
* it is read by both.
|
|
20
|
-
*/
|
|
1
|
+
/** Shared source-edit wire contracts, used by browser authoring and the
|
|
2
|
+
* Node serving plugin without importing either implementation graph. */
|
|
21
3
|
|
|
22
4
|
export type { PreparedSourceEdit, SourceEditRequest, StructReparentContext } from '@volter/sdk/source-authoring';
|
package/src/source/ts-ast.ts
CHANGED
|
@@ -1,72 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Not an abstraction over the TypeScript compiler API — each function here
|
|
5
|
-
* hands back the compiler's OWN node and gets out of the way, and every caller
|
|
6
|
-
* keeps programming against `ts.*`. What they remove is the retyping: the
|
|
7
|
-
* eleven modules in this directory that read a TSX module each spelled the same
|
|
8
|
-
* four or five little walks out again, and the copies had begun to disagree
|
|
9
|
-
* about things that matter.
|
|
10
|
-
*
|
|
11
|
-
* What "matters" means, concretely — this is the drift these functions existed
|
|
12
|
-
* in nine, three, three, two and two copies of, and what each copy got wrong:
|
|
13
|
-
*
|
|
14
|
-
* - {@link parseAuthoringTsx}: `ts.createSourceFile(file, code,
|
|
15
|
-
* ScriptTarget.Latest, true, ScriptKind.TSX)` appeared at NINE sites, two of
|
|
16
|
-
* them already wrapped in a local `parseTsx`/`sourceFile` whose only
|
|
17
|
-
* difference was argument order. Both of the last two arguments are silent
|
|
18
|
-
* if wrong: `setParentNodes: false` leaves `node.parent` undefined, which
|
|
19
|
-
* breaks every upward walk here (including {@link enclosingScope}) at
|
|
20
|
-
* runtime rather than at compile time, and a missing `ScriptKind.TSX`
|
|
21
|
-
* parses JSX as type assertions.
|
|
22
|
-
* - {@link hasModifier}: two copies, ALREADY drifted —
|
|
23
|
-
* `plan-fork-component.ts`'s guarded with `ts.canHaveModifiers` and
|
|
24
|
-
* `r3f-project-contracts.ts`'s cast the node to `ts.HasModifiers` and hoped.
|
|
25
|
-
* The guarded form is the one kept.
|
|
26
|
-
* - {@link enclosingScope}: three copies of one loop, returning
|
|
27
|
-
* `FunctionLikeDeclaration` in two and `SignatureDeclaration` in the third
|
|
28
|
-
* for the same four node kinds.
|
|
29
|
-
* - {@link numericLiteral}: three copies (`numeric`, `numericLiteral`) of the
|
|
30
|
-
* unary-sign rule — the part a fourth author would plausibly write without,
|
|
31
|
-
* silently losing every negative literal.
|
|
32
|
-
* - {@link jsxAttribute} / {@link refIdentifier}: two and three copies of
|
|
33
|
-
* "find this attribute", one of the `refIdentifier`s returning `undefined`
|
|
34
|
-
* where the others return `null`.
|
|
35
|
-
*
|
|
36
|
-
* Deliberately NOT here: anything whose two versions differ for a caller's own
|
|
37
|
-
* reason. `plan-fork-component.ts`'s `jsxAtOffset` and `reparent-guard.ts`'s
|
|
38
|
-
* `jsxElementAt` look like twins and are not — one wants the OPENING element to
|
|
39
|
-
* read its tag, the other wants the whole `ts.JsxElement` to move it, and the
|
|
40
|
-
* second prunes the walk by span while the first does not.
|
|
41
|
-
*/
|
|
1
|
+
/** Shared TypeScript AST helpers. Callers retain compiler nodes, parent
|
|
2
|
+
* pointers, and JSX syntax for their own source analyses. */
|
|
42
3
|
|
|
43
4
|
import * as ts from 'typescript';
|
|
44
5
|
|
|
45
|
-
/**
|
|
46
|
-
*
|
|
47
|
-
* needs it: newest syntax, parent pointers set, JSX enabled.
|
|
48
|
-
*
|
|
49
|
-
* `file` is the module's path — it is what the compiler reports in positions
|
|
50
|
-
* and what `node.getText(sf)` reads against, so pass the real one where there
|
|
51
|
-
* is one.
|
|
52
|
-
*/
|
|
53
|
-
/**
|
|
54
|
-
* ONE parse per (file, bytes) — the same source is parsed several times over
|
|
55
|
-
* for one bundle otherwise.
|
|
56
|
-
*
|
|
57
|
-
* `ProjectContractResolver` calls this three times for a single file
|
|
58
|
-
* (`importedForSource`, `typeAliasesForSource`, `propSpecsForSource`), the
|
|
59
|
-
* stamping transform parses it again, and every one of those runs on EVERY
|
|
60
|
-
* structural edit.
|
|
61
|
-
* Measured on `examples/third-person`, the contract walk alone costs 380ms of
|
|
62
|
-
* the ~1.1s an edit spends on CPU.
|
|
63
|
-
*
|
|
64
|
-
* Keyed on the BYTES, so it is sound in a way an analysis cache is not: a
|
|
65
|
-
* parse is a pure function of its input, while what a file MEANS also depends
|
|
66
|
-
* on the files it imports. Edited bytes miss the cache and re-parse; the entry
|
|
67
|
-
* for the old bytes is dropped, so the map holds one node tree per live file
|
|
68
|
-
* rather than one per revision.
|
|
69
|
-
*/
|
|
6
|
+
/** Parse TSX with current syntax and parent pointers. Cache one parse per
|
|
7
|
+
* file and exact source text; changed bytes replace the previous tree. */
|
|
70
8
|
const parsedByFile = new Map<
|
|
71
9
|
string,
|
|
72
10
|
{ readonly code: string; readonly sourceFile: ts.SourceFile }
|
|
@@ -86,14 +24,6 @@ export function parseAuthoringTsx(file: string, code: string): ts.SourceFile {
|
|
|
86
24
|
return sourceFile;
|
|
87
25
|
}
|
|
88
26
|
|
|
89
|
-
/** Drop every cached parse — for a project switch, where the next project's
|
|
90
|
-
* files may reuse these paths with different bytes. Bytes are compared on
|
|
91
|
-
* every hit anyway, so this is hygiene against unbounded growth, not
|
|
92
|
-
* correctness. */
|
|
93
|
-
export function clearAuthoringParseCache(): void {
|
|
94
|
-
parsedByFile.clear();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
27
|
/** Whether `node` carries a given modifier (`export`, `default`, …). False for
|
|
98
28
|
* a node kind that cannot carry modifiers at all, rather than throwing. */
|
|
99
29
|
export function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
|
|
@@ -124,29 +54,9 @@ export function enclosingScope(node: ts.Node): ts.FunctionLikeDeclaration | null
|
|
|
124
54
|
return null;
|
|
125
55
|
}
|
|
126
56
|
|
|
127
|
-
/**
|
|
128
|
-
* The number an expression literally IS, or `undefined` when it is anything
|
|
129
|
-
* computed. A leading `-`/`+` counts: `position={[-1, 0, 2]}` is authored
|
|
130
|
-
* constantly and its first entry is a `PrefixUnaryExpression`, not a numeric
|
|
131
|
-
* literal.
|
|
132
|
-
*/
|
|
133
|
-
export function numericLiteral(expression: ts.Expression): number | undefined {
|
|
134
|
-
if (ts.isNumericLiteral(expression)) return Number(expression.text);
|
|
135
|
-
if (
|
|
136
|
-
ts.isPrefixUnaryExpression(expression) &&
|
|
137
|
-
(expression.operator === ts.SyntaxKind.MinusToken ||
|
|
138
|
-
expression.operator === ts.SyntaxKind.PlusToken) &&
|
|
139
|
-
ts.isNumericLiteral(expression.operand)
|
|
140
|
-
) {
|
|
141
|
-
const value = Number(expression.operand.text);
|
|
142
|
-
return expression.operator === ts.SyntaxKind.MinusToken ? -value : value;
|
|
143
|
-
}
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
57
|
/** The named attribute on a JSX opening element, or `undefined`. Spread
|
|
148
58
|
* attributes are not attributes and are skipped. */
|
|
149
|
-
|
|
59
|
+
function jsxAttribute(
|
|
150
60
|
element: ts.JsxOpeningLikeElement,
|
|
151
61
|
name: string,
|
|
152
62
|
): ts.JsxAttribute | undefined {
|
|
@@ -1,35 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Why it exists: the router used to pick the Tailwind arbitrary-value class
|
|
6
|
-
* (`bg-[#059669]`) whenever an element merely HAD a literal `className`, with no
|
|
7
|
-
* evidence anything would ever compile that class. On a plain-CSS project the
|
|
8
|
-
* write lands in source and paints NOTHING — measured on the vendored pmndrs
|
|
9
|
-
* racing-game (plain Vite + `src/styles.css`, no tailwind in its tree or ours):
|
|
10
|
-
* the editor's visible result was only the adapter's optimistic inline patch, a
|
|
11
|
-
* cold remount rendered `rgba(0, 0, 0, 0)`, and the game's own build has no
|
|
12
|
-
* transformer for it either. Inert bytes in someone's source that tell a lie
|
|
13
|
-
* about their game.
|
|
14
|
-
*
|
|
15
|
-
* The fix is the same observation-over-inference rule the OID dialect decision
|
|
16
|
-
* follows: route on what was OBSERVED about the project, not on what the
|
|
17
|
-
* element's shape suggests. A tier gathers evidence with whatever I/O it has
|
|
18
|
-
* (`node:fs` on the dev server, `StorageBackend` in the hosted browser) and
|
|
19
|
-
* hands the bytes here; the decision itself is pure and lives ONLY here, so the
|
|
20
|
-
* two tiers cannot drift into different answers.
|
|
21
|
-
*
|
|
22
|
-
* **The probe is deliberately strict, and the asymmetry is why.** A false
|
|
23
|
-
* NEGATIVE costs an inline `style={{…}}` write — a less idiomatic expression of
|
|
24
|
-
* the same edit that paints correctly in every project, in and out of the
|
|
25
|
-
* editor. A false POSITIVE costs a write that is a lie. So "a dependency is
|
|
26
|
-
* listed" is not enough on its own: something must also show the pipeline is
|
|
27
|
-
* actually wired (a config file, a postcss/vite/astro plugin mention, or a css
|
|
28
|
-
* entry that imports the framework). A CDN/runtime interpreter in HTML stands
|
|
29
|
-
* alone because it needs no build step at all.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/** The routing answer, plus what was actually observed to produce it. */
|
|
1
|
+
/** Determine whether project evidence proves that utility classes are compiled
|
|
2
|
+
* or interpreted. A declared dependency needs matching pipeline configuration;
|
|
3
|
+
* a CDN interpreter needs no build step. Unproven support routes style writes
|
|
4
|
+
* to inline CSS. The probe is pure; callers supply the files they can read. */
|
|
33
5
|
export interface UtilityClassSupport {
|
|
34
6
|
/** True only when the project was OBSERVED to interpret utility classes. */
|
|
35
7
|
readonly supported: boolean;
|