@animus-ui/vite-plugin 0.1.4 → 0.1.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"build-start.d.ts","sourceRoot":"","sources":["../src/build-start.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAC/D,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,MAAM,GAC3D,OAAO,CAAC,IAAI,CAAC,CAkRf"}
1
+ {"version":3,"file":"build-start.d.ts","sourceRoot":"","sources":["../src/build-start.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAC/D,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,MAAM,GAC3D,OAAO,CAAC,IAAI,CAAC,CAuRf"}
@@ -1,9 +1,31 @@
1
+ /**
2
+ * The namespace every virtual module this plugin owns lives under. Exported so
3
+ * the `transform` guard that keeps those modules out of source-file handling
4
+ * matches on the same string the ids are built from — a rename of the
5
+ * namespace cannot leave the guard behind.
6
+ */
7
+ export declare const VIRTUAL_PREFIX = "virtual:animus/";
1
8
  export declare const VIRTUAL_CSS_ID = "virtual:animus/styles.css";
2
9
  export declare const RESOLVED_CSS_ID = "\0virtual:animus/styles.css";
3
10
  export declare const VIRTUAL_COMPONENTS_ID = "virtual:animus/components.js";
4
11
  export declare const RESOLVED_COMPONENTS_ID = "\0virtual:animus/components.js";
5
12
  export declare const VIRTUAL_BRIDGE_ID = "virtual:animus/hmr-bridge.js";
6
13
  export declare const RESOLVED_BRIDGE_ID = "\0virtual:animus/hmr-bridge.js";
14
+ /**
15
+ * Browser-addressable URL for the bridge, used as the `src` of the dev
16
+ * `<script type="module">` (see index-html.ts). THE explanation of the `/@id/`
17
+ * convention lives here; everything else points at it.
18
+ *
19
+ * Vite reserves `/@id/` for module ids that are not file paths. Its transform
20
+ * middleware normalizes such a URL before serving it (`unwrapId`): strip the
21
+ * prefix, then decode the `__x00__` placeholder back to a NUL byte. The
22
+ * UNPREFIXED specifier is what travels here — `resolveVirtualId` is the hook
23
+ * that answers it, and it answers with the `\0` form. (Vite's own `wrapId`
24
+ * encodes an already-`\0`-prefixed id as `__x00__`; that form resolves too, but
25
+ * it leans on the placeholder convention rather than on this plugin's own
26
+ * `resolveId` contract.)
27
+ */
28
+ export declare const BRIDGE_SCRIPT_SRC = "/@id/virtual:animus/hmr-bridge.js";
7
29
  export declare const VIRTUAL_SYSTEM_PROPS_ID = "virtual:animus/system-props";
8
30
  export declare const RESOLVED_SYSTEM_PROPS_ID = "\0virtual:animus/system-props";
9
31
  export declare const DEFAULT_EXCLUDE: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,8BAA8B,CAAC;AAC1D,eAAO,MAAM,eAAe,gCAAgC,CAAC;AAE7D,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AACpE,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AAEvE,eAAO,MAAM,iBAAiB,iCAAiC,CAAC;AAChE,eAAO,MAAM,kBAAkB,mCAAmC,CAAC;AAEnE,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AACrE,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAExE,eAAO,MAAM,eAAe,UAA+C,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD,eAAO,MAAM,cAAc,8BAAgC,CAAC;AAC5D,eAAO,MAAM,eAAe,gCAAwB,CAAC;AAErD,eAAO,MAAM,qBAAqB,iCAAmC,CAAC;AACtE,eAAO,MAAM,sBAAsB,mCAA+B,CAAC;AAEnE,eAAO,MAAM,iBAAiB,iCAAmC,CAAC;AAClE,eAAO,MAAM,kBAAkB,mCAA2B,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,sCAA8B,CAAC;AAE7D,eAAO,MAAM,uBAAuB,gCAAkC,CAAC;AACvE,eAAO,MAAM,wBAAwB,kCAAiC,CAAC;AAEvE,eAAO,MAAM,eAAe,UAA+C,CAAC"}
package/dist/context.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { HotUpdateEvents } from './hot-update-events';
2
2
  import type { LightningTargets } from './css';
3
3
  import type { AnimusExtractOptions } from './index';
4
- import type { ExternalPackageOutcome, SystemConfig } from '@animus-ui/extract/pipeline';
4
+ import type { ExternalPackageOutcome, ManifestDiagnostic, SystemConfig } from '@animus-ui/extract/pipeline';
5
5
  import type { Logger } from 'vite';
6
6
  /**
7
7
  * Structured per-layer CSS sheets from the Rust crate (dev split delivery).
@@ -33,6 +33,45 @@ export declare function buildFileEntriesFromCache(cache: Map<string, {
33
33
  source: string;
34
34
  hash: string;
35
35
  }>;
36
+ /**
37
+ * The exact source `virtual:animus/system-props` serves for the current state.
38
+ * One definition, so the served bytes and the change decision below can never
39
+ * be computed from different inputs.
40
+ *
41
+ * A real context carries the module already generated (`systemPropsModuleMemo`,
42
+ * refreshed wherever the four inputs move), so serving it and deciding whether
43
+ * it changed are both reads. Contexts that publish those inputs by hand — the
44
+ * behavioral test doubles — carry no memo and generate here instead.
45
+ */
46
+ export declare function systemPropsModuleSource(ctx: PluginContext): string;
47
+ /**
48
+ * Run project analysis and report whether the served system-props module
49
+ * CHANGED.
50
+ *
51
+ * The module is imported by every module that renders a system prop, so
52
+ * re-delivering it pushes an update through all of them; every analysis
53
+ * republishes its inputs whether or not they moved, so a new analysis is not
54
+ * itself an admissible trigger (openspec: vite-extraction-plugin, "System prop
55
+ * map HMR invalidation").
56
+ *
57
+ * The comparison is over the GENERATED MODULE, not over the prop map alone.
58
+ * The map is one of four inputs, and they move independently: widening a
59
+ * component's `.system({ ... })` opt-in adds a `dynamicPropConfig` entry while
60
+ * minting no new utility class, so a map-only comparison reports "unchanged"
61
+ * and the client is left with a config missing the new prop — permanently,
62
+ * since Vite keeps serving the module's cached transform result across full
63
+ * page reloads. Comparing the artifact itself needs no argument about which
64
+ * inputs are volatile this month.
65
+ *
66
+ * A free function rather than a method: the comparison must run for real
67
+ * against any context the caller holds, including the behavioral test doubles
68
+ * that stand in for the engine.
69
+ */
70
+ export declare function runAnalysisTrackingSystemProps(ctx: PluginContext, fileEntries: Array<{
71
+ path: string;
72
+ source: string;
73
+ hash?: string;
74
+ }>): boolean;
36
75
  /**
37
76
  * Drop a deleted (or renamed-away) file from the dev file cache so its
38
77
  * last-known source stops riding along as a ghost entry on every later
@@ -52,7 +91,7 @@ export declare function pruneFileCache(cache: Map<string, {
52
91
  * index.ts only wires Vite hooks to those functions.
53
92
  *
54
93
  * A class rather than closure variables so each hook module names exactly
55
- * the state it touches, and the engine store (DEF-1: per-instance, never
94
+ * the state it touches, and the engine store (per-instance, never
56
95
  * module-level) is explicit.
57
96
  */
58
97
  export declare class PluginContext {
@@ -74,16 +113,24 @@ export declare class PluginContext {
74
113
  resolvedComponentCss: string;
75
114
  storedSheets: CssSheets | null;
76
115
  layerDeclaration: string;
77
- fragmentCache: Map<string, {
78
- base?: string;
79
- variants?: string;
80
- compounds?: string;
81
- states?: string;
82
- }>;
116
+ transformOutputHashes: Map<string, string>;
117
+ recordTransformOutput(relativePath: string, code: string): void;
118
+ /**
119
+ * Merge `Keyframes` collections from discovered external package entries
120
+ * into the system's collections (keyframes-only carve-out — the consumer
121
+ * system stays the singular config authority). Runs after buildStart
122
+ * discovery AND after every geological-reset system reload, since a reload
123
+ * rebuilds `this.system` from the consumer entry alone.
124
+ */
125
+ applyExternalKeyframes(): void;
126
+ /** Discovery-time keyframes diagnostics awaiting the next analysis's
127
+ * shared surfacing pass. */
128
+ externalKeyframesDiagnostics: ManifestDiagnostic[];
83
129
  reverseProvenance: Record<string, string[]>;
84
130
  storedSystemPropMapJson: string;
85
131
  storedDynamicPropsJson: string;
86
132
  storedTransformsSource: string;
133
+ systemPropsModuleMemo: string | null;
87
134
  fileCache: Map<string, {
88
135
  hash: string;
89
136
  source: string;
@@ -100,7 +147,6 @@ export declare class PluginContext {
100
147
  externalSourceEntries: Map<string, string>;
101
148
  externalPackageOutcomes: ExternalPackageOutcome[];
102
149
  devServer: any;
103
- bridgeInjected: boolean;
104
150
  resolvedSystemPath: string | null;
105
151
  systemDependencyKeys: Set<string>;
106
152
  systemDependencyPaths: string[];
@@ -111,6 +157,13 @@ export declare class PluginContext {
111
157
  readonly engineApi: () => any;
112
158
  constructor(options: AnimusExtractOptions, engineApiOverride?: () => any);
113
159
  log(msg: string): void;
160
+ /**
161
+ * Standard-level information — emitted whether or not `verbose` is on.
162
+ * Reserved for events a developer must see without opting in, e.g. a file
163
+ * created after buildStart being folded into the analysis (openspec:
164
+ * hmr-new-file-detection, "New file detection logging").
165
+ */
166
+ info(msg: string): void;
114
167
  warn(msg: string): void;
115
168
  logTimingWaterfall(timing: Record<string, number>): void;
116
169
  /**
@@ -197,6 +250,13 @@ export declare class PluginContext {
197
250
  * `getModuleById` searches the client AND ssr environment graphs and its
198
251
  * `invalidateModule` invalidates both instances behind the returned node,
199
252
  * which is exactly the reach this path wants. It stays the seam here.
253
+ *
254
+ * Both modules are invalidated unconditionally, and deliberately so: the
255
+ * appearance or disappearance of a whole component file is not the
256
+ * steady-state edit the change-gated path governs, and a client reload does
257
+ * NOT rescue a module that was never invalidated — Vite keeps serving its
258
+ * cached transform result across reloads (openspec: hmr-new-file-detection,
259
+ * "CSS invalidation after new file analysis").
200
260
  */
201
261
  invalidateExtractedModules(): void;
202
262
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
package/dist/hmr.d.ts CHANGED
@@ -19,5 +19,5 @@ import type { DevEnvironment, EnvironmentModuleNode, HotUpdateOptions } from 'vi
19
19
  * graph, so system dependencies registered through `watcher.add` outside the
20
20
  * root still reach the reset branch, with an empty `modules` list.
21
21
  */
22
- export declare function handleHotUpdate(ctx: PluginContext, environment: DevEnvironment, { type, file, timestamp, modules }: HotUpdateOptions): Promise<EnvironmentModuleNode[] | void>;
22
+ export declare function handleHotUpdate(ctx: PluginContext, environment: DevEnvironment, { type, file, timestamp, modules, read }: HotUpdateOptions): Promise<EnvironmentModuleNode[] | void>;
23
23
  //# sourceMappingURL=hmr.d.ts.map
package/dist/hmr.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,MAAM,CAAC;AAEd;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,WAAW,EAAE,cAAc,EAC3B,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,gBAAgB,GACnD,OAAO,CAAC,qBAAqB,EAAE,GAAG,IAAI,CAAC,CAgEzC"}
1
+ {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,MAAM,CAAC;AAEd;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,WAAW,EAAE,cAAc,EAC3B,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,GACzD,OAAO,CAAC,qBAAqB,EAAE,GAAG,IAAI,CAAC,CAmEzC"}
@@ -9,7 +9,19 @@
9
9
  * - `analyzed` — re-analysis ran; every environment invalidates the affected
10
10
  * modules in its own graph. `staleDefinitionFiles` are rootDir-relative
11
11
  * definition files whose component replacement changed (the changed file
12
- * itself is never listed).
12
+ * itself is never listed). `systemPropsChanged` reports whether the served
13
+ * system-props module moved (see `runAnalysisTrackingSystemProps` in
14
+ * context.ts) — only the owning dispatch holds the before/after values, so
15
+ * it travels with the decision rather than being re-derived per environment.
16
+ * `presentationOnly` reports that the changed file's transform output is
17
+ * byte-identical before and after the edit (style values are not part of
18
+ * the emitted replacement — class names hash `filename::binding`): every
19
+ * environment then excludes the file's own modules from the update it
20
+ * returns, so the module never re-executes and React component identity,
21
+ * generated IDs, focus, and DOM-owned state survive the edit. Computed once
22
+ * by the owning dispatch (it needs the pre-edit source and manifest, which
23
+ * the owner consumed while re-analyzing) — the same travel rule as
24
+ * `systemPropsChanged`.
13
25
  */
14
26
  export type HotUpdateResult = {
15
27
  kind: 'ignored';
@@ -18,6 +30,8 @@ export type HotUpdateResult = {
18
30
  } | {
19
31
  kind: 'analyzed';
20
32
  staleDefinitionFiles: string[];
33
+ systemPropsChanged: boolean;
34
+ presentationOnly: boolean;
21
35
  };
22
36
  /**
23
37
  * Once-per-file-event coordination across the `hotUpdate` dispatches.
@@ -1 +1 @@
1
- {"version":3,"file":"hot-update-events.d.ts","sourceRoot":"","sources":["../src/hot-update-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,oBAAoB,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,eAAe;IAGd,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAE9D,YAA6B,YAAY,SAAK,EAAI;IAElD,0EAA0E;IAC1E,KAAK,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUvE;IAED,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAErE;IAED,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAEzD;CACF"}
1
+ {"version":3,"file":"hot-update-events.d.ts","sourceRoot":"","sources":["../src/hot-update-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,eAAe;IAGd,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAE9D,YAA6B,YAAY,SAAK,EAAI;IAElD,0EAA0E;IAC1E,KAAK,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUvE;IAED,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAErE;IAED,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAEzD;CACF"}
@@ -3,16 +3,18 @@ import type { HtmlTagDescriptor } from 'vite';
3
3
  /**
4
4
  * transformIndexHtml: build the head-prepend tag set.
5
5
  *
6
- * Two independent tags, both delivery-only:
6
+ * Three independent tags, all delivery-only:
7
7
  * - the appearance bootstrap script, when the host build passed a
8
8
  * pre-generated artifact via `appearanceBootstrap` (absent otherwise);
9
- * - the `@layer` declaration style, when `buildStart` computed one.
9
+ * - the `@layer` declaration style, when `buildStart` computed one;
10
+ * - the HMR bridge module script, in dev only.
10
11
  *
11
- * Both branches are guarded on non-empty CONTENT, not on mere presence: an
12
- * artifact whose `code` is empty is a caller defect, and emitting
12
+ * The two build-time branches are guarded on non-empty CONTENT, not on mere
13
+ * presence: an artifact whose `code` is empty is a caller defect, and emitting
13
14
  * `<script data-animus-bootstrap></script>` for it would break the
14
15
  * unconfigured-parity contract in spirit (a tag that does nothing) while
15
- * hiding the defect. Symmetric with `layerDeclaration`.
16
+ * hiding the defect. Symmetric with `layerDeclaration`. The bridge has no
17
+ * content of its own — it is a `src` reference — so its only guard is the mode.
16
18
  *
17
19
  * Ordering: every descriptor rides the same `head-prepend` bucket, which Vite
18
20
  * serializes in array order (`serializeTags`) and splices in immediately after
@@ -20,8 +22,8 @@ import type { HtmlTagDescriptor } from 'vite';
20
22
  * before the layer declaration and — since built stylesheet links are appended
21
23
  * at `</head>` — before any stylesheet reference.
22
24
  *
23
- * When neither input is set the array is empty, so an unconfigured build emits
24
- * no tag, attribute, or whitespace of its own.
25
+ * With neither build-time input set, a production build emits no tag,
26
+ * attribute, or whitespace of its own.
25
27
  */
26
28
  export declare function buildIndexHtmlTags(ctx: PluginContext): HtmlTagDescriptor[];
27
29
  //# sourceMappingURL=index-html.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-html.d.ts","sourceRoot":"","sources":["../src/index-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,iBAAiB,EAAE,CAuB1E"}
1
+ {"version":3,"file":"index-html.d.ts","sourceRoot":"","sources":["../src/index-html.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,iBAAiB,EAAE,CAyD1E"}
package/dist/index.cjs CHANGED
@@ -7,13 +7,36 @@ let fs = require("fs");
7
7
  let path = require("path");
8
8
  let crypto = require("crypto");
9
9
  //#region src/constants.ts
10
- const VIRTUAL_CSS_ID = "virtual:animus/styles.css";
11
- const RESOLVED_CSS_ID = "\0virtual:animus/styles.css";
12
- const VIRTUAL_COMPONENTS_ID = "virtual:animus/components.js";
13
- const RESOLVED_COMPONENTS_ID = "\0virtual:animus/components.js";
14
- const VIRTUAL_BRIDGE_ID = "virtual:animus/hmr-bridge.js";
15
- const RESOLVED_BRIDGE_ID = "\0virtual:animus/hmr-bridge.js";
16
- const RESOLVED_SYSTEM_PROPS_ID = "\0virtual:animus/system-props";
10
+ /**
11
+ * The namespace every virtual module this plugin owns lives under. Exported so
12
+ * the `transform` guard that keeps those modules out of source-file handling
13
+ * matches on the same string the ids are built from — a rename of the
14
+ * namespace cannot leave the guard behind.
15
+ */
16
+ const VIRTUAL_PREFIX = "virtual:animus/";
17
+ const VIRTUAL_CSS_ID = `${VIRTUAL_PREFIX}styles.css`;
18
+ const RESOLVED_CSS_ID = `\0${VIRTUAL_CSS_ID}`;
19
+ const VIRTUAL_COMPONENTS_ID = `${VIRTUAL_PREFIX}components.js`;
20
+ const RESOLVED_COMPONENTS_ID = `\0${VIRTUAL_COMPONENTS_ID}`;
21
+ const VIRTUAL_BRIDGE_ID = `${VIRTUAL_PREFIX}hmr-bridge.js`;
22
+ const RESOLVED_BRIDGE_ID = `\0${VIRTUAL_BRIDGE_ID}`;
23
+ /**
24
+ * Browser-addressable URL for the bridge, used as the `src` of the dev
25
+ * `<script type="module">` (see index-html.ts). THE explanation of the `/@id/`
26
+ * convention lives here; everything else points at it.
27
+ *
28
+ * Vite reserves `/@id/` for module ids that are not file paths. Its transform
29
+ * middleware normalizes such a URL before serving it (`unwrapId`): strip the
30
+ * prefix, then decode the `__x00__` placeholder back to a NUL byte. The
31
+ * UNPREFIXED specifier is what travels here — `resolveVirtualId` is the hook
32
+ * that answers it, and it answers with the `\0` form. (Vite's own `wrapId`
33
+ * encodes an already-`\0`-prefixed id as `__x00__`; that form resolves too, but
34
+ * it leans on the placeholder convention rather than on this plugin's own
35
+ * `resolveId` contract.)
36
+ */
37
+ const BRIDGE_SCRIPT_SRC = `/@id/${VIRTUAL_BRIDGE_ID}`;
38
+ const VIRTUAL_SYSTEM_PROPS_ID = `${VIRTUAL_PREFIX}system-props`;
39
+ const RESOLVED_SYSTEM_PROPS_ID = `\0${VIRTUAL_SYSTEM_PROPS_ID}`;
17
40
  const DEFAULT_EXCLUDE = [
18
41
  "node_modules",
19
42
  "dist",
@@ -129,6 +152,7 @@ async function runBuildStart(ctx, resolveSpecifier, emitAsset) {
129
152
  }
130
153
  ctx.externalPackageDirs = collected.packageDirs;
131
154
  ctx.registerSystemWatchPaths();
155
+ ctx.applyExternalKeyframes();
132
156
  const packageFileCount = fileEntries.length - localFileCount;
133
157
  ctx.log(`Discovered ${fileEntries.length} files (${packageFileCount} from packages) (${Math.round(performance.now() - t0)}ms)`);
134
158
  t0 = performance.now();
@@ -349,6 +373,56 @@ function buildFileEntriesFromCache(cache, changedPath) {
349
373
  });
350
374
  return entries;
351
375
  }
376
+ /** Generate the module from the four inputs the context currently holds. */
377
+ function generateSystemPropsModule(ctx) {
378
+ return (0, _animus_ui_extract_pipeline.buildSystemPropsModule)({
379
+ systemPropMapJson: ctx.storedSystemPropMapJson,
380
+ groupRegistryJson: ctx.system.groupRegistryJson,
381
+ dynamicProps: JSON.parse(ctx.storedDynamicPropsJson),
382
+ transformsSource: ctx.storedTransformsSource
383
+ });
384
+ }
385
+ /**
386
+ * The exact source `virtual:animus/system-props` serves for the current state.
387
+ * One definition, so the served bytes and the change decision below can never
388
+ * be computed from different inputs.
389
+ *
390
+ * A real context carries the module already generated (`systemPropsModuleMemo`,
391
+ * refreshed wherever the four inputs move), so serving it and deciding whether
392
+ * it changed are both reads. Contexts that publish those inputs by hand — the
393
+ * behavioral test doubles — carry no memo and generate here instead.
394
+ */
395
+ function systemPropsModuleSource(ctx) {
396
+ return ctx.systemPropsModuleMemo ??= generateSystemPropsModule(ctx);
397
+ }
398
+ /**
399
+ * Run project analysis and report whether the served system-props module
400
+ * CHANGED.
401
+ *
402
+ * The module is imported by every module that renders a system prop, so
403
+ * re-delivering it pushes an update through all of them; every analysis
404
+ * republishes its inputs whether or not they moved, so a new analysis is not
405
+ * itself an admissible trigger (openspec: vite-extraction-plugin, "System prop
406
+ * map HMR invalidation").
407
+ *
408
+ * The comparison is over the GENERATED MODULE, not over the prop map alone.
409
+ * The map is one of four inputs, and they move independently: widening a
410
+ * component's `.system({ ... })` opt-in adds a `dynamicPropConfig` entry while
411
+ * minting no new utility class, so a map-only comparison reports "unchanged"
412
+ * and the client is left with a config missing the new prop — permanently,
413
+ * since Vite keeps serving the module's cached transform result across full
414
+ * page reloads. Comparing the artifact itself needs no argument about which
415
+ * inputs are volatile this month.
416
+ *
417
+ * A free function rather than a method: the comparison must run for real
418
+ * against any context the caller holds, including the behavioral test doubles
419
+ * that stand in for the engine.
420
+ */
421
+ function runAnalysisTrackingSystemProps(ctx, fileEntries) {
422
+ const before = systemPropsModuleSource(ctx);
423
+ ctx.runAnalysis(fileEntries);
424
+ return systemPropsModuleSource(ctx) !== before;
425
+ }
352
426
  /**
353
427
  * Drop a deleted (or renamed-away) file from the dev file cache so its
354
428
  * last-known source stops riding along as a ghost entry on every later
@@ -368,7 +442,7 @@ function pruneFileCache(cache, rootDir, absPath) {
368
442
  * index.ts only wires Vite hooks to those functions.
369
443
  *
370
444
  * A class rather than closure variables so each hook module names exactly
371
- * the state it touches, and the engine store (DEF-1: per-instance, never
445
+ * the state it touches, and the engine store (per-instance, never
372
446
  * module-level) is explicit.
373
447
  */
374
448
  var PluginContext = class {
@@ -390,11 +464,31 @@ var PluginContext = class {
390
464
  resolvedComponentCss = "";
391
465
  storedSheets = null;
392
466
  layerDeclaration = "";
393
- fragmentCache = /* @__PURE__ */ new Map();
467
+ transformOutputHashes = /* @__PURE__ */ new Map();
468
+ recordTransformOutput(relativePath, code) {
469
+ this.transformOutputHashes.set(relativePath, (0, _animus_ui_extract_pipeline.contentHash)(code));
470
+ }
471
+ /**
472
+ * Merge `Keyframes` collections from discovered external package entries
473
+ * into the system's collections (keyframes-only carve-out — the consumer
474
+ * system stays the singular config authority). Runs after buildStart
475
+ * discovery AND after every geological-reset system reload, since a reload
476
+ * rebuilds `this.system` from the consumer entry alone.
477
+ */
478
+ applyExternalKeyframes() {
479
+ if (this.externalSourceEntries.size === 0) return;
480
+ const merge = (0, _animus_ui_extract_pipeline.mergeExternalKeyframes)((entry, root) => this.engineApi().scanKeyframesExports(entry, root), this.system.keyframesJson, this.externalSourceEntries.values(), this.rootDir);
481
+ this.system.keyframesJson = merge.keyframesJson;
482
+ this.externalKeyframesDiagnostics = merge.diagnostics;
483
+ }
484
+ /** Discovery-time keyframes diagnostics awaiting the next analysis's
485
+ * shared surfacing pass. */
486
+ externalKeyframesDiagnostics = [];
394
487
  reverseProvenance = {};
395
488
  storedSystemPropMapJson = "{}";
396
489
  storedDynamicPropsJson = "{}";
397
490
  storedTransformsSource = "{}";
491
+ systemPropsModuleMemo = null;
398
492
  fileCache = /* @__PURE__ */ new Map();
399
493
  hotUpdateEvents = new HotUpdateEvents();
400
494
  packageMap = {};
@@ -408,7 +502,6 @@ var PluginContext = class {
408
502
  externalSourceEntries = /* @__PURE__ */ new Map();
409
503
  externalPackageOutcomes = [];
410
504
  devServer;
411
- bridgeInjected = false;
412
505
  resolvedSystemPath = null;
413
506
  systemDependencyKeys = /* @__PURE__ */ new Set();
414
507
  systemDependencyPaths = [];
@@ -456,6 +549,15 @@ var PluginContext = class {
456
549
  log(msg) {
457
550
  if (this.verbose) (this.logger ?? console).info(`[animus] ${msg}`);
458
551
  }
552
+ /**
553
+ * Standard-level information — emitted whether or not `verbose` is on.
554
+ * Reserved for events a developer must see without opting in, e.g. a file
555
+ * created after buildStart being folded into the analysis (openspec:
556
+ * hmr-new-file-detection, "New file detection logging").
557
+ */
558
+ info(msg) {
559
+ (this.logger ?? console).info(`[animus] ${msg}`);
560
+ }
459
561
  warn(msg) {
460
562
  (this.logger ?? console).warn(`[animus] ${msg}`);
461
563
  }
@@ -487,10 +589,12 @@ var PluginContext = class {
487
589
  this.systemDependencyKeys = keys;
488
590
  this.systemDependencyPaths = deps;
489
591
  this.registerSystemWatchPaths();
592
+ this.applyExternalKeyframes();
490
593
  } catch (e) {
491
594
  if (this.options.strict) throw new Error(`[animus-extract] Failed to load system from ${this.resolvedSystemPath}: ${e}`, { cause: e });
492
595
  console.warn(`[animus-extract] Failed to load system from ${this.resolvedSystemPath}:`, e);
493
596
  }
597
+ this.systemPropsModuleMemo = generateSystemPropsModule(this);
494
598
  }
495
599
  /**
496
600
  * Run project analysis via the shared `runProjectAnalysis` and update
@@ -510,18 +614,14 @@ var PluginContext = class {
510
614
  staticCssJson: this.staticCssJson,
511
615
  externalDirs: this.externalPackageDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
512
616
  devMode: !this.isProd,
513
- warn: (m) => this.warn(m)
617
+ warn: (m) => this.warn(m),
618
+ strict: this.options.strict,
619
+ extraDiagnostics: this.externalKeyframesDiagnostics
514
620
  });
515
621
  this.storedManifest = result.manifest;
516
622
  this.storedManifestJson = result.manifestJson;
517
623
  this.storedSystemPropMapJson = JSON.stringify(result.manifest?.system_prop_map ?? {});
518
624
  this.storedDynamicPropsJson = JSON.stringify(result.manifest?.dynamic_props ?? {});
519
- this.bridgeInjected = false;
520
- const newFragments = result.manifest?.component_fragments;
521
- if (newFragments && typeof newFragments === "object") {
522
- this.fragmentCache.clear();
523
- for (const [id, sheets] of Object.entries(newFragments)) this.fragmentCache.set(id, sheets);
524
- }
525
625
  this.reverseProvenance = result.manifest?.reverse_provenance ?? {};
526
626
  this.storedSheets = result.manifest?.sheets ?? null;
527
627
  this.globalCss = result.globalCss;
@@ -531,6 +631,7 @@ var PluginContext = class {
531
631
  console.warn("[animus-extract] analyzeProject failed:", e);
532
632
  return;
533
633
  }
634
+ this.systemPropsModuleMemo = generateSystemPropsModule(this);
534
635
  this.applyAssetSubstitutions();
535
636
  this.enforceExternalTokenContracts();
536
637
  }
@@ -665,6 +766,13 @@ var PluginContext = class {
665
766
  * `getModuleById` searches the client AND ssr environment graphs and its
666
767
  * `invalidateModule` invalidates both instances behind the returned node,
667
768
  * which is exactly the reach this path wants. It stays the seam here.
769
+ *
770
+ * Both modules are invalidated unconditionally, and deliberately so: the
771
+ * appearance or disappearance of a whole component file is not the
772
+ * steady-state edit the change-gated path governs, and a client reload does
773
+ * NOT rescue a module that was never invalidated — Vite keeps serving its
774
+ * cached transform result across reloads (openspec: hmr-new-file-detection,
775
+ * "CSS invalidation after new file analysis").
668
776
  */
669
777
  invalidateExtractedModules() {
670
778
  const server = this.devServer;
@@ -741,6 +849,82 @@ var PluginContext = class {
741
849
  }
742
850
  };
743
851
  //#endregion
852
+ //#region src/transform.ts
853
+ /**
854
+ * The dev-mode bridge prepend, shared with the hot-update gate so both
855
+ * compute byte-identical served output. The import goes AFTER the directive
856
+ * prologue the engine hoists to byte 0 — prepending above it would demote
857
+ * 'use client'/'use strict' to an ordinary expression statement, silently
858
+ * un-marking client modules on exactly the RSC-capable hosts this delivery
859
+ * path serves.
860
+ */
861
+ function applyDevBridgeImport(code) {
862
+ const prologue = /^(?:(['"])use [a-z -]+\1;?\r?\n)*/.exec(code)?.[0] ?? "";
863
+ return prologue + `import '${VIRTUAL_BRIDGE_ID}';\n` + code.slice(prologue.length);
864
+ }
865
+ /**
866
+ * transform: replace builder chains with `createComponent()` calls using
867
+ * the pre-built manifest; detect files created after buildStart and fold
868
+ * them into the analysis.
869
+ *
870
+ * The HMR bridge is NOT injected here — `transformIndexHtml` delivers it as a
871
+ * `<script type="module">` per served document (openspec:
872
+ * dev-stylesheet-management, "HMR bridge auto-injected in dev mode"; "Transform
873
+ * emitter unchanged" forbids the emitter importing it).
874
+ */
875
+ function transformSource(ctx, code, id) {
876
+ if (!ctx.storedManifest) return null;
877
+ if (id.startsWith("\0") || id.startsWith("virtual:animus/")) return null;
878
+ const isExternalPkg = ctx.externalPackageDirs.some((dir) => id.startsWith(dir + path.sep) || id === dir);
879
+ const relativePath = (0, path.relative)(ctx.rootDir, id);
880
+ if (!isExternalPkg) {
881
+ if (!/\.[jt]sx?$/.test(id)) return null;
882
+ if (id.includes("node_modules")) return null;
883
+ if (relativePath.startsWith("..") || (0, path.isAbsolute)(relativePath)) return null;
884
+ }
885
+ if (!ctx.storedManifest.files?.[relativePath]?.length) {
886
+ if (!ctx.isProd && !ctx.fileCache.has(relativePath)) {
887
+ if (isExternalPkg) {
888
+ const owner = Object.entries(ctx.externalDirOwners).find(([dir]) => id === dir || id.startsWith(dir + path.sep));
889
+ if (owner) ctx.externalFileOwners[relativePath] = owner[1];
890
+ }
891
+ const hash = (0, _animus_ui_extract_pipeline.contentHash)(code);
892
+ ctx.fileCache.set(relativePath, {
893
+ hash,
894
+ source: code
895
+ });
896
+ const fileEntries = buildFileEntriesFromCache(ctx.fileCache, relativePath);
897
+ ctx.runAnalysis(fileEntries);
898
+ const compCount = ctx.storedManifest.files?.[relativePath]?.length ?? 0;
899
+ ctx.info(`New file detected: ${relativePath} — ${compCount ? `${compCount} components extracted` : "no components"}`);
900
+ ctx.invalidateExtractedModules();
901
+ }
902
+ if (!ctx.storedManifest.files?.[relativePath]?.length) return null;
903
+ }
904
+ try {
905
+ const { transformFile } = ctx.engineApi();
906
+ const result = transformFile(code, relativePath, ctx.storedManifestJson);
907
+ if (!result.hasComponents) return null;
908
+ if (ctx.verbose) {
909
+ const compCount = ctx.storedManifest.files?.[relativePath]?.length ?? 0;
910
+ ctx.log(`transform ${relativePath}: ${compCount} components`);
911
+ }
912
+ let outputCode = result.code;
913
+ if (!ctx.isProd) {
914
+ outputCode = applyDevBridgeImport(result.code);
915
+ ctx.recordTransformOutput(relativePath, outputCode);
916
+ }
917
+ return {
918
+ code: outputCode,
919
+ map: null
920
+ };
921
+ } catch (e) {
922
+ if (ctx.options.strict) throw new Error(`[animus-extract] Failed to transform ${id}: ${e}`, { cause: e });
923
+ console.warn(`[animus-extract] Failed to transform ${id}:`, e);
924
+ return null;
925
+ }
926
+ }
927
+ //#endregion
744
928
  //#region src/hmr.ts
745
929
  /**
746
930
  * hotUpdate: the single dev file-event hook. Handles system-dependency
@@ -761,13 +945,16 @@ var PluginContext = class {
761
945
  * graph, so system dependencies registered through `watcher.add` outside the
762
946
  * root still reach the reset branch, with an empty `modules` list.
763
947
  */
764
- async function handleHotUpdate(ctx, environment, { type, file, timestamp, modules }) {
948
+ async function handleHotUpdate(ctx, environment, { type, file, timestamp, modules, read }) {
765
949
  if (ctx.isProd) return;
766
950
  const ownsEvent = ctx.hotUpdateEvents.claim(environment.name, file, timestamp);
767
951
  const absFile = (0, path.resolve)(file);
768
952
  ctx.log(`hotUpdate ${type} ${(0, path.relative)(ctx.rootDir, absFile)} env=${environment.name} owns=${ownsEvent}`);
769
953
  if (ctx.isSystemDependency(absFile)) {
770
- if (ownsEvent) ctx.requestGeologicalReset((0, path.relative)(ctx.rootDir, absFile));
954
+ if (ownsEvent) {
955
+ await reconcileSourceEntry(ctx, absFile, type, read);
956
+ ctx.requestGeologicalReset((0, path.relative)(ctx.rootDir, absFile));
957
+ }
771
958
  return [];
772
959
  }
773
960
  if (type === "create") return;
@@ -775,18 +962,69 @@ async function handleHotUpdate(ctx, environment, { type, file, timestamp, module
775
962
  if (ownsEvent) pruneDeletedFile(ctx, absFile);
776
963
  return;
777
964
  }
778
- if (ownsEvent) ctx.hotUpdateEvents.record(file, timestamp, await analyzeChangedFile(ctx, file, absFile));
965
+ if (ownsEvent) ctx.hotUpdateEvents.record(file, timestamp, await analyzeChangedFile(ctx, file, absFile, read));
779
966
  const result = ctx.hotUpdateEvents.resultOf(file, timestamp);
780
967
  if (result.kind === "ignored") return;
781
968
  if (result.kind === "unchanged") return [];
782
- return invalidateStaleModules(ctx, environment, modules, result.staleDefinitionFiles);
969
+ return invalidateStaleModules(ctx, environment, modules, result);
970
+ }
971
+ /**
972
+ * The changed file's text.
973
+ *
974
+ * Editors save atomically — truncate, then rewrite — so a watcher event can
975
+ * arrive while the path is momentarily EMPTY. Vite's `read()` helper retries on
976
+ * empty content for exactly that reason; reading the path directly at the same
977
+ * moment yields `''`, and since the corrective content produces no second
978
+ * event, that empty source would be cached permanently.
979
+ *
980
+ * Vite always supplies `read`, so the direct read is NOT a fallback for Vite:
981
+ * it exists for hosts that drive this hook without one. The dev-lane's adapter
982
+ * contract is deliberately bundler-neutral, and a second runtime satisfying it
983
+ * must not be forced to fabricate a helper to get correct behavior.
984
+ */
985
+ async function readChangedSource(absFile, read) {
986
+ return read ? await read() : (0, fs.readFileSync)(absFile, "utf-8");
987
+ }
988
+ /**
989
+ * Reconcile the `fileCache` entry of a file that is BOTH a system dependency
990
+ * and a discovered component source, since the dependency branch is terminal —
991
+ * neither the edit path's cache write nor `pruneDeletedFile` runs for it.
992
+ *
993
+ * An edit refreshes the entry: `performGeologicalReset` rebuilds its
994
+ * full-source analysis from this cache, so a stale entry would be re-analyzed
995
+ * on every later reset. A delete prunes it, exactly as the ordinary delete path
996
+ * would (openspec: hmr-new-file-detection, "Watcher deletion pruning") — a
997
+ * surviving entry is a ghost source no watcher event can ever name again.
998
+ *
999
+ * Dependency-only files have no entry: `pruneFileCache` no-ops for them, and
1000
+ * the edit path must not create one — the cache is the component-source set,
1001
+ * and a phantom entry would feed the engine a file it never discovered.
1002
+ */
1003
+ async function reconcileSourceEntry(ctx, absFile, type, read) {
1004
+ if (type === "delete") {
1005
+ pruneFileCache(ctx.fileCache, ctx.rootDir, absFile);
1006
+ return;
1007
+ }
1008
+ const relPath = (0, path.relative)(ctx.rootDir, absFile);
1009
+ if (!ctx.fileCache.has(relPath)) return;
1010
+ let source;
1011
+ try {
1012
+ source = await readChangedSource(absFile, read);
1013
+ } catch {
1014
+ ctx.warn(`could not re-read ${relPath} after a system-dependency edit — geological resets will analyze its pre-edit text until a later edit is read successfully`);
1015
+ return;
1016
+ }
1017
+ ctx.fileCache.set(relPath, {
1018
+ hash: (0, _animus_ui_extract_pipeline.contentHash)(source),
1019
+ source
1020
+ });
783
1021
  }
784
1022
  /**
785
1023
  * The once-per-event analysis half: gate the file, diff its content hash,
786
1024
  * refresh the cache, and re-run project analysis. The returned result is what
787
1025
  * the remaining environments act on.
788
1026
  */
789
- async function analyzeChangedFile(ctx, file, absFile) {
1027
+ async function analyzeChangedFile(ctx, file, absFile, read) {
790
1028
  const ext = (0, path.extname)(file);
791
1029
  if (!ctx.extensionsSet.has(ext)) return { kind: "ignored" };
792
1030
  const excludePatterns = ctx.options.exclude ?? DEFAULT_EXCLUDE;
@@ -794,7 +1032,7 @@ async function analyzeChangedFile(ctx, file, absFile) {
794
1032
  const relPath = (0, path.relative)(ctx.rootDir, absFile);
795
1033
  let source;
796
1034
  try {
797
- source = (0, fs.readFileSync)(absFile, "utf-8");
1035
+ source = await readChangedSource(absFile, read);
798
1036
  } catch {
799
1037
  return { kind: "ignored" };
800
1038
  }
@@ -840,8 +1078,7 @@ async function analyzeChangedFile(ctx, file, absFile) {
840
1078
  }
841
1079
  if (ctx.verbose && invalidatedIds.size > directComponentIds.length) ctx.log(`HMR: ${directComponentIds.length} direct + ${invalidatedIds.size - directComponentIds.length} transitive components invalidated`);
842
1080
  const analysisStart = performance.now();
843
- const fileEntries = buildFileEntriesFromCache(ctx.fileCache, relPath);
844
- ctx.runAnalysis(fileEntries);
1081
+ const systemPropsChanged = runAnalysisTrackingSystemProps(ctx, buildFileEntriesFromCache(ctx.fileCache, relPath));
845
1082
  const analysisMs = Math.round(performance.now() - analysisStart);
846
1083
  const staleDefinitionFiles = [];
847
1084
  if (ctx.storedManifest?.components) {
@@ -852,15 +1089,44 @@ async function analyzeChangedFile(ctx, file, absFile) {
852
1089
  staleDefinitionFiles.push(defFile);
853
1090
  }
854
1091
  }
1092
+ const presentationOnly = isPresentationOnlyEdit(ctx, scannerRelPath, source);
855
1093
  const hmrMs = Math.round(performance.now() - hmrStart);
856
- ctx.log(`HMR update: ${relPath} — analysis ${analysisMs}ms, total ${hmrMs}ms`);
1094
+ ctx.log(`HMR update: ${relPath} — analysis ${analysisMs}ms, total ${hmrMs}ms${presentationOnly ? " (presentation-only)" : ""}`);
857
1095
  ctx.logTimingWaterfall(ctx.storedManifest?.timing ?? {});
858
1096
  return {
859
1097
  kind: "analyzed",
860
- staleDefinitionFiles
1098
+ staleDefinitionFiles,
1099
+ systemPropsChanged,
1100
+ presentationOnly
861
1101
  };
862
1102
  }
863
1103
  /**
1104
+ * True when the changed file's transform output is byte-identical across the
1105
+ * edit — a presentation-only change (style values are not part of the emitted
1106
+ * replacement, whose class names hash `filename::binding`).
1107
+ *
1108
+ * Compares a POST-analysis re-transform of the file (the engine transforms
1109
+ * from its retained facts, so this runs against the just-updated analysis)
1110
+ * with the hash of what the module LAST SERVED (`recordTransformOutput` in
1111
+ * the transform hook, bridge import included). Comparing the full served
1112
+ * output — never replacement strings — is what keeps a mixed style+code edit
1113
+ * deliverable. Fails open: a transform error, a file the manifest does not
1114
+ * list, or a module that never served (no recorded hash) delivers normally.
1115
+ */
1116
+ function isPresentationOnlyEdit(ctx, scannerRelPath, source) {
1117
+ const servedHash = ctx.transformOutputHashes.get(scannerRelPath);
1118
+ if (!servedHash || !ctx.storedManifestJson) return false;
1119
+ if (!ctx.storedManifest?.files?.[scannerRelPath]?.length) return false;
1120
+ try {
1121
+ const { transformFile } = ctx.engineApi();
1122
+ const fresh = transformFile(source, scannerRelPath, ctx.storedManifestJson);
1123
+ if (!fresh.hasComponents) return false;
1124
+ return (0, _animus_ui_extract_pipeline.contentHash)(applyDevBridgeImport(fresh.code)) === servedHash;
1125
+ } catch {
1126
+ return false;
1127
+ }
1128
+ }
1129
+ /**
864
1130
  * Reconcile a deleted file in dev.
865
1131
  *
866
1132
  * Without this the removed file's last-known source stays in `ctx.fileCache`,
@@ -880,17 +1146,19 @@ function pruneDeletedFile(ctx, absFile) {
880
1146
  * (virtual:animus/styles.css) is NOT invalidated here — it only changes on a
881
1147
  * geological reset (vars/globals are stable during dev).
882
1148
  */
883
- function invalidateStaleModules(ctx, environment, modules, staleDefinitionFiles) {
1149
+ function invalidateStaleModules(ctx, environment, modules, analyzed) {
884
1150
  const graph = environment.moduleGraph;
885
- const modulesToUpdate = [...modules];
886
- for (const moduleId of [RESOLVED_COMPONENTS_ID, RESOLVED_SYSTEM_PROPS_ID]) {
1151
+ const modulesToUpdate = analyzed.presentationOnly ? [] : [...modules];
1152
+ const moduleIds = [RESOLVED_COMPONENTS_ID];
1153
+ if (analyzed.systemPropsChanged) moduleIds.push(RESOLVED_SYSTEM_PROPS_ID);
1154
+ for (const moduleId of moduleIds) {
887
1155
  const mod = graph.getModuleById(moduleId);
888
1156
  if (mod) {
889
1157
  graph.invalidateModule(mod);
890
1158
  modulesToUpdate.push(mod);
891
1159
  }
892
1160
  }
893
- for (const defFile of staleDefinitionFiles) {
1161
+ for (const defFile of analyzed.staleDefinitionFiles) {
894
1162
  const absDefPath = (0, path.resolve)(ctx.rootDir, defFile);
895
1163
  const defModule = graph.getModuleById(absDefPath) ?? graph.getModulesByFile(absDefPath)?.values().next().value;
896
1164
  if (defModule) {
@@ -899,6 +1167,11 @@ function invalidateStaleModules(ctx, environment, modules, staleDefinitionFiles)
899
1167
  modulesToUpdate.push(defModule);
900
1168
  }
901
1169
  }
1170
+ if (analyzed.presentationOnly) {
1171
+ ctx.log(`HMR (${environment.name}): presentation-only — ${modules.length} module update(s) suppressed, CSS delivered`);
1172
+ for (const mod of modules) if (mod.url) environment.transformRequest?.(mod.url)?.catch(() => {});
1173
+ return modulesToUpdate;
1174
+ }
902
1175
  const invalidated = modulesToUpdate.length - modules.length;
903
1176
  if (invalidated > 0) {
904
1177
  ctx.log(`HMR (${environment.name}): ${invalidated} modules invalidated`);
@@ -910,16 +1183,18 @@ function invalidateStaleModules(ctx, environment, modules, staleDefinitionFiles)
910
1183
  /**
911
1184
  * transformIndexHtml: build the head-prepend tag set.
912
1185
  *
913
- * Two independent tags, both delivery-only:
1186
+ * Three independent tags, all delivery-only:
914
1187
  * - the appearance bootstrap script, when the host build passed a
915
1188
  * pre-generated artifact via `appearanceBootstrap` (absent otherwise);
916
- * - the `@layer` declaration style, when `buildStart` computed one.
1189
+ * - the `@layer` declaration style, when `buildStart` computed one;
1190
+ * - the HMR bridge module script, in dev only.
917
1191
  *
918
- * Both branches are guarded on non-empty CONTENT, not on mere presence: an
919
- * artifact whose `code` is empty is a caller defect, and emitting
1192
+ * The two build-time branches are guarded on non-empty CONTENT, not on mere
1193
+ * presence: an artifact whose `code` is empty is a caller defect, and emitting
920
1194
  * `<script data-animus-bootstrap><\/script>` for it would break the
921
1195
  * unconfigured-parity contract in spirit (a tag that does nothing) while
922
- * hiding the defect. Symmetric with `layerDeclaration`.
1196
+ * hiding the defect. Symmetric with `layerDeclaration`. The bridge has no
1197
+ * content of its own — it is a `src` reference — so its only guard is the mode.
923
1198
  *
924
1199
  * Ordering: every descriptor rides the same `head-prepend` bucket, which Vite
925
1200
  * serializes in array order (`serializeTags`) and splices in immediately after
@@ -927,8 +1202,8 @@ function invalidateStaleModules(ctx, environment, modules, staleDefinitionFiles)
927
1202
  * before the layer declaration and — since built stylesheet links are appended
928
1203
  * at `</head>` — before any stylesheet reference.
929
1204
  *
930
- * When neither input is set the array is empty, so an unconfigured build emits
931
- * no tag, attribute, or whitespace of its own.
1205
+ * With neither build-time input set, a production build emits no tag,
1206
+ * attribute, or whitespace of its own.
932
1207
  */
933
1208
  function buildIndexHtmlTags(ctx) {
934
1209
  const tags = [];
@@ -945,74 +1220,25 @@ function buildIndexHtmlTags(ctx) {
945
1220
  children: ctx.layerDeclaration,
946
1221
  injectTo: "head-prepend"
947
1222
  });
1223
+ if (!ctx.isProd) tags.push({
1224
+ tag: "script",
1225
+ attrs: {
1226
+ type: "module",
1227
+ src: BRIDGE_SCRIPT_SRC,
1228
+ "data-animus-bridge": ""
1229
+ },
1230
+ injectTo: "head-prepend"
1231
+ });
948
1232
  return tags;
949
1233
  }
950
1234
  //#endregion
951
- //#region src/transform.ts
952
- /**
953
- * transform: replace builder chains with `createComponent()` calls using
954
- * the pre-built manifest; inject the HMR bridge import once in dev; detect
955
- * files created after buildStart and fold them into the analysis.
956
- */
957
- function transformSource(ctx, code, id) {
958
- if (!ctx.storedManifest) return null;
959
- const isExternalPkg = ctx.externalPackageDirs.some((dir) => id.startsWith(dir + path.sep) || id === dir);
960
- if (!isExternalPkg) {
961
- if (!/\.[jt]sx?$/.test(id)) return null;
962
- if (id.includes("node_modules")) return null;
963
- }
964
- const relativePath = (0, path.relative)(ctx.rootDir, id);
965
- if (!ctx.storedManifest.files?.[relativePath]?.length) {
966
- if (!ctx.isProd && !ctx.fileCache.has(relativePath)) {
967
- if (isExternalPkg) {
968
- const owner = Object.entries(ctx.externalDirOwners).find(([dir]) => id === dir || id.startsWith(dir + path.sep));
969
- if (owner) ctx.externalFileOwners[relativePath] = owner[1];
970
- }
971
- const hash = (0, _animus_ui_extract_pipeline.contentHash)(code);
972
- ctx.fileCache.set(relativePath, {
973
- hash,
974
- source: code
975
- });
976
- const fileEntries = buildFileEntriesFromCache(ctx.fileCache, relativePath);
977
- ctx.runAnalysis(fileEntries);
978
- const compCount = ctx.storedManifest.files?.[relativePath]?.length ?? 0;
979
- ctx.log(`New file detected: ${relativePath} — ${compCount ? `${compCount} components extracted` : "no components"}`);
980
- if (compCount) ctx.invalidateExtractedModules();
981
- }
982
- if (!ctx.storedManifest.files?.[relativePath]?.length) return null;
983
- }
984
- try {
985
- const { transformFile } = ctx.engineApi();
986
- const result = transformFile(code, relativePath, ctx.storedManifestJson);
987
- if (!result.hasComponents) return null;
988
- let transformedCode = result.code;
989
- if (!ctx.isProd && !ctx.bridgeInjected && ctx.storedSheets) {
990
- transformedCode = `import '${VIRTUAL_BRIDGE_ID}';\n${transformedCode}`;
991
- ctx.bridgeInjected = true;
992
- ctx.log("HMR bridge injected via transform");
993
- }
994
- if (ctx.verbose) {
995
- const compCount = ctx.storedManifest.files?.[relativePath]?.length ?? 0;
996
- ctx.log(`transform ${relativePath}: ${compCount} components`);
997
- }
998
- return {
999
- code: transformedCode,
1000
- map: null
1001
- };
1002
- } catch (e) {
1003
- if (ctx.options.strict) throw new Error(`[animus-extract] Failed to transform ${id}: ${e}`, { cause: e });
1004
- console.warn(`[animus-extract] Failed to transform ${id}:`, e);
1005
- return null;
1006
- }
1007
- }
1008
- //#endregion
1009
1235
  //#region src/virtual-modules.ts
1010
1236
  /** resolveId: map virtual ids and redirect external DS package imports. */
1011
1237
  function resolveVirtualId(ctx, id) {
1012
- if (id === "virtual:animus/styles.css") return RESOLVED_CSS_ID;
1013
- if (id === "virtual:animus/components.js") return RESOLVED_COMPONENTS_ID;
1014
- if (id === "virtual:animus/hmr-bridge.js") return RESOLVED_BRIDGE_ID;
1015
- if (id === "virtual:animus/system-props") return RESOLVED_SYSTEM_PROPS_ID;
1238
+ if (id === VIRTUAL_CSS_ID) return RESOLVED_CSS_ID;
1239
+ if (id === VIRTUAL_COMPONENTS_ID) return RESOLVED_COMPONENTS_ID;
1240
+ if (id === VIRTUAL_BRIDGE_ID) return RESOLVED_BRIDGE_ID;
1241
+ if (id === VIRTUAL_SYSTEM_PROPS_ID) return RESOLVED_SYSTEM_PROPS_ID;
1016
1242
  const srcEntry = ctx.externalSourceEntries.get(id);
1017
1243
  if (srcEntry) return srcEntry;
1018
1244
  return null;
@@ -1025,7 +1251,7 @@ function loadVirtualModule(ctx, id) {
1025
1251
  targets: ctx.lcssTargets,
1026
1252
  warnFn: (msg) => ctx.warn(msg)
1027
1253
  };
1028
- if (id === "\0virtual:animus/styles.css") {
1254
+ if (id === RESOLVED_CSS_ID) {
1029
1255
  if (!ctx.isProd && ctx.storedSheets) {
1030
1256
  const { variables, body } = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
1031
1257
  layers: ctx.options.layers,
@@ -1047,36 +1273,44 @@ function loadVirtualModule(ctx, id) {
1047
1273
  });
1048
1274
  return [variables, (0, _animus_ui_extract_pipeline.postProcessCss)(body, lcssOpts)].filter(Boolean).join("\n");
1049
1275
  }
1050
- if (id === "\0virtual:animus/components.js") return `export default \`${(0, _animus_ui_extract_pipeline.postProcessCss)((0, _animus_ui_extract_pipeline.stripLeadingLayerDeclaration)(ctx.resolvedComponentCss || ""), {
1276
+ if (id === RESOLVED_COMPONENTS_ID) return `export default \`${(0, _animus_ui_extract_pipeline.postProcessCss)((0, _animus_ui_extract_pipeline.stripLeadingLayerDeclaration)(ctx.resolvedComponentCss || ""), {
1051
1277
  ...lcssOpts,
1052
1278
  minify: false
1053
1279
  }).replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\`;`;
1054
- if (id === "\0virtual:animus/hmr-bridge.js") return `
1280
+ if (id === RESOLVED_BRIDGE_ID) return `
1055
1281
  import css from '${VIRTUAL_COMPONENTS_ID}';
1056
1282
 
1057
1283
  const GLOBAL_KEY = '__animus_sheet_${(0, crypto.createHash)("md5").update(ctx.options.system).digest("hex").slice(0, 8)}__';
1058
1284
  let sheet = globalThis[GLOBAL_KEY] || null;
1059
1285
 
1060
- if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in document) {
1061
- if (!sheet) {
1062
- sheet = new CSSStyleSheet();
1063
- globalThis[GLOBAL_KEY] = sheet;
1064
- document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
1065
- }
1066
- sheet.replaceSync(css);
1067
- } else {
1068
- // Fallback: inject or update <style> tag
1069
- let el = document.querySelector('style[data-animus-components]');
1070
- if (!el) {
1071
- el = document.createElement('style');
1072
- el.setAttribute('data-animus-components', '');
1073
- document.head.appendChild(el);
1286
+ // Server evaluation is a no-op: SSR hosts reach this module through the
1287
+ // bridge import prepended to transformed component modules, and only the
1288
+ // browser pass owns a document.
1289
+ if (typeof document !== 'undefined') {
1290
+ if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in document) {
1291
+ if (!sheet) {
1292
+ sheet = new CSSStyleSheet();
1293
+ globalThis[GLOBAL_KEY] = sheet;
1294
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
1295
+ }
1296
+ sheet.replaceSync(css);
1297
+ } else {
1298
+ // Fallback: inject or update <style> tag
1299
+ let el = document.querySelector('style[data-animus-components]');
1300
+ if (!el) {
1301
+ el = document.createElement('style');
1302
+ el.setAttribute('data-animus-components', '');
1303
+ document.head.appendChild(el);
1304
+ }
1305
+ el.textContent = css;
1074
1306
  }
1075
- el.textContent = css;
1076
1307
  }
1077
1308
 
1078
1309
  if (import.meta.hot) {
1079
1310
  import.meta.hot.accept('${VIRTUAL_COMPONENTS_ID}', (newModule) => {
1311
+ // The server module runner has a hot channel too; only the browser
1312
+ // pass owns a document to update.
1313
+ if (typeof document === 'undefined') return;
1080
1314
  if (sheet) {
1081
1315
  sheet.replaceSync(newModule.default);
1082
1316
  } else {
@@ -1086,12 +1320,7 @@ if (import.meta.hot) {
1086
1320
  });
1087
1321
  }
1088
1322
  `;
1089
- if (id === "\0virtual:animus/system-props") return (0, _animus_ui_extract_pipeline.buildSystemPropsModule)({
1090
- systemPropMapJson: ctx.storedSystemPropMapJson,
1091
- groupRegistryJson: ctx.system.groupRegistryJson,
1092
- dynamicProps: JSON.parse(ctx.storedDynamicPropsJson),
1093
- transformsSource: ctx.storedTransformsSource
1094
- });
1323
+ if (id === RESOLVED_SYSTEM_PROPS_ID) return systemPropsModuleSource(ctx);
1095
1324
  return null;
1096
1325
  }
1097
1326
  //#endregion
@@ -1107,6 +1336,9 @@ function animusExtract(options) {
1107
1336
  return {
1108
1337
  name: "animus-extract",
1109
1338
  enforce: "pre",
1339
+ config(_config, env) {
1340
+ return { define: { __ANIMUS_DEV__: env.command !== "build" } };
1341
+ },
1110
1342
  configureServer(server) {
1111
1343
  ctx.devServer = server;
1112
1344
  ctx.registerSystemWatchPaths();
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qGAAqG;IACrG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAmEnE;eAEc,aAAa"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qGAAqG;IACrG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CA0EnE;eAEc,aAAa"}
@@ -1,8 +1,22 @@
1
1
  import type { PluginContext } from './context';
2
+ /**
3
+ * The dev-mode bridge prepend, shared with the hot-update gate so both
4
+ * compute byte-identical served output. The import goes AFTER the directive
5
+ * prologue the engine hoists to byte 0 — prepending above it would demote
6
+ * 'use client'/'use strict' to an ordinary expression statement, silently
7
+ * un-marking client modules on exactly the RSC-capable hosts this delivery
8
+ * path serves.
9
+ */
10
+ export declare function applyDevBridgeImport(code: string): string;
2
11
  /**
3
12
  * transform: replace builder chains with `createComponent()` calls using
4
- * the pre-built manifest; inject the HMR bridge import once in dev; detect
5
- * files created after buildStart and fold them into the analysis.
13
+ * the pre-built manifest; detect files created after buildStart and fold
14
+ * them into the analysis.
15
+ *
16
+ * The HMR bridge is NOT injected here — `transformIndexHtml` delivers it as a
17
+ * `<script type="module">` per served document (openspec:
18
+ * dev-stylesheet-management, "HMR bridge auto-injected in dev mode"; "Transform
19
+ * emitter unchanged" forbids the emitter importing it).
6
20
  */
7
21
  export declare function transformSource(ctx: PluginContext, code: string, id: string): {
8
22
  code: string;
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CAuFpC"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CAqHpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-modules.d.ts","sourceRoot":"","sources":["../src/virtual-modules.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,aAAa,EAClB,EAAE,EAAE,MAAM,GACT,MAAM,GAAG,IAAI,CAYf;AAED;wDACwD;AACxD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,EAAE,EAAE,MAAM,GACT,MAAM,GAAG,IAAI,CAyGf"}
1
+ {"version":3,"file":"virtual-modules.d.ts","sourceRoot":"","sources":["../src/virtual-modules.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,aAAa,EAClB,EAAE,EAAE,MAAM,GACT,MAAM,GAAG,IAAI,CAYf;AAED;wDACwD;AACxD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,EAAE,EAAE,MAAM,GACT,MAAM,GAAG,IAAI,CA8Gf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animus-ui/vite-plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Animus static CSS extraction Vite plugin",
5
5
  "keywords": [
6
6
  "animus",
@@ -39,7 +39,7 @@
39
39
  "compile": "tsc --noEmit"
40
40
  },
41
41
  "dependencies": {
42
- "@animus-ui/extract": "0.1.4",
42
+ "@animus-ui/extract": "0.1.6",
43
43
  "browserslist": "^4.24.0",
44
44
  "lightningcss": "^1.29.0"
45
45
  },