@pyreon/compiler 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/index.js +418 -18
  3. package/lib/types/index.d.ts +92 -1
  4. package/package.json +13 -12
  5. package/src/index.ts +2 -1
  6. package/src/jsx.ts +669 -17
  7. package/src/tests/backend-parity-r7-r9.test.ts +91 -0
  8. package/src/tests/backend-prop-derived-callback-divergence.test.ts +74 -0
  9. package/src/tests/collapse-bail-census.test.ts +245 -0
  10. package/src/tests/collapse-key-source-hygiene.test.ts +88 -0
  11. package/src/tests/element-valued-const-child.test.ts +61 -0
  12. package/src/tests/falsy-child-characterization.test.ts +48 -0
  13. package/src/tests/malformed-input-resilience.test.ts +50 -0
  14. package/src/tests/partial-collapse-detector.test.ts +121 -0
  15. package/src/tests/partial-collapse-emit.test.ts +104 -0
  16. package/src/tests/partial-collapse-robustness.test.ts +53 -0
  17. package/src/tests/prop-derived-shadow.test.ts +96 -0
  18. package/src/tests/pure-call-reactive-args.test.ts +50 -0
  19. package/src/tests/r13-callback-stmt-equivalence.test.ts +58 -0
  20. package/src/tests/r14-ssr-mode-parity.test.ts +51 -0
  21. package/src/tests/r15-elemconst-propderived.test.ts +47 -0
  22. package/src/tests/r19-defer-inline-robust.test.ts +54 -0
  23. package/src/tests/r20-backend-equivalence-sweep.test.ts +50 -0
  24. package/src/tests/rocketstyle-collapse.test.ts +208 -0
  25. package/src/tests/signal-autocall-shadow.test.ts +86 -0
  26. package/src/tests/sourcemap-fidelity.test.ts +77 -0
  27. package/src/tests/static-text-baking.test.ts +64 -0
  28. package/src/tests/transform-state-isolation.test.ts +49 -0
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"949f24ff-1","name":"defer-inline.ts"},{"uid":"949f24ff-3","name":"event-names.ts"},{"uid":"949f24ff-5","name":"load-native.ts"},{"uid":"949f24ff-7","name":"jsx.ts"},{"uid":"949f24ff-9","name":"pyreon-intercept.ts"},{"uid":"949f24ff-11","name":"reactivity-lens.ts"},{"uid":"949f24ff-13","name":"project-scanner.ts"},{"uid":"949f24ff-15","name":"react-intercept.ts"},{"uid":"949f24ff-17","name":"test-audit.ts"},{"uid":"949f24ff-19","name":"island-audit.ts"},{"uid":"949f24ff-21","name":"ssg-audit.ts"},{"uid":"949f24ff-23","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"949f24ff-1":{"renderedLength":15789,"gzipLength":5000,"brotliLength":0,"metaUid":"949f24ff-0"},"949f24ff-3":{"renderedLength":2941,"gzipLength":1335,"brotliLength":0,"metaUid":"949f24ff-2"},"949f24ff-5":{"renderedLength":3959,"gzipLength":1744,"brotliLength":0,"metaUid":"949f24ff-4"},"949f24ff-7":{"renderedLength":46803,"gzipLength":11368,"brotliLength":0,"metaUid":"949f24ff-6"},"949f24ff-9":{"renderedLength":29316,"gzipLength":9273,"brotliLength":0,"metaUid":"949f24ff-8"},"949f24ff-11":{"renderedLength":4373,"gzipLength":2130,"brotliLength":0,"metaUid":"949f24ff-10"},"949f24ff-13":{"renderedLength":4762,"gzipLength":1730,"brotliLength":0,"metaUid":"949f24ff-12"},"949f24ff-15":{"renderedLength":28896,"gzipLength":7322,"brotliLength":0,"metaUid":"949f24ff-14"},"949f24ff-17":{"renderedLength":13167,"gzipLength":5060,"brotliLength":0,"metaUid":"949f24ff-16"},"949f24ff-19":{"renderedLength":18208,"gzipLength":6051,"brotliLength":0,"metaUid":"949f24ff-18"},"949f24ff-21":{"renderedLength":12753,"gzipLength":4173,"brotliLength":0,"metaUid":"949f24ff-20"},"949f24ff-23":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"949f24ff-22"}},"nodeMetas":{"949f24ff-0":{"id":"/src/defer-inline.ts","moduleParts":{"index.js":"949f24ff-1"},"imported":[{"uid":"949f24ff-24"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-2":{"id":"/src/event-names.ts","moduleParts":{"index.js":"949f24ff-3"},"imported":[],"importedBy":[{"uid":"949f24ff-6"}]},"949f24ff-4":{"id":"/src/load-native.ts","moduleParts":{"index.js":"949f24ff-5"},"imported":[{"uid":"949f24ff-28"},{"uid":"949f24ff-29"},{"uid":"949f24ff-26"}],"importedBy":[{"uid":"949f24ff-6"}]},"949f24ff-6":{"id":"/src/jsx.ts","moduleParts":{"index.js":"949f24ff-7"},"imported":[{"uid":"949f24ff-24"},{"uid":"949f24ff-2"},{"uid":"949f24ff-4"}],"importedBy":[{"uid":"949f24ff-22"},{"uid":"949f24ff-10"}]},"949f24ff-8":{"id":"/src/pyreon-intercept.ts","moduleParts":{"index.js":"949f24ff-9"},"imported":[{"uid":"949f24ff-27"}],"importedBy":[{"uid":"949f24ff-22"},{"uid":"949f24ff-10"}]},"949f24ff-10":{"id":"/src/reactivity-lens.ts","moduleParts":{"index.js":"949f24ff-11"},"imported":[{"uid":"949f24ff-6"},{"uid":"949f24ff-8"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-12":{"id":"/src/project-scanner.ts","moduleParts":{"index.js":"949f24ff-13"},"imported":[{"uid":"949f24ff-25"},{"uid":"949f24ff-26"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-14":{"id":"/src/react-intercept.ts","moduleParts":{"index.js":"949f24ff-15"},"imported":[{"uid":"949f24ff-27"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-16":{"id":"/src/test-audit.ts","moduleParts":{"index.js":"949f24ff-17"},"imported":[{"uid":"949f24ff-25"},{"uid":"949f24ff-26"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-18":{"id":"/src/island-audit.ts","moduleParts":{"index.js":"949f24ff-19"},"imported":[{"uid":"949f24ff-25"},{"uid":"949f24ff-26"},{"uid":"949f24ff-27"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-20":{"id":"/src/ssg-audit.ts","moduleParts":{"index.js":"949f24ff-21"},"imported":[{"uid":"949f24ff-25"},{"uid":"949f24ff-26"},{"uid":"949f24ff-27"}],"importedBy":[{"uid":"949f24ff-22"}]},"949f24ff-22":{"id":"/src/index.ts","moduleParts":{"index.js":"949f24ff-23"},"imported":[{"uid":"949f24ff-0"},{"uid":"949f24ff-6"},{"uid":"949f24ff-10"},{"uid":"949f24ff-12"},{"uid":"949f24ff-14"},{"uid":"949f24ff-8"},{"uid":"949f24ff-16"},{"uid":"949f24ff-18"},{"uid":"949f24ff-20"}],"importedBy":[],"isEntry":true},"949f24ff-24":{"id":"oxc-parser","moduleParts":{},"imported":[],"importedBy":[{"uid":"949f24ff-0"},{"uid":"949f24ff-6"}]},"949f24ff-25":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"949f24ff-12"},{"uid":"949f24ff-16"},{"uid":"949f24ff-18"},{"uid":"949f24ff-20"}]},"949f24ff-26":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"949f24ff-12"},{"uid":"949f24ff-16"},{"uid":"949f24ff-18"},{"uid":"949f24ff-20"},{"uid":"949f24ff-4"}]},"949f24ff-27":{"id":"typescript","moduleParts":{},"imported":[],"importedBy":[{"uid":"949f24ff-14"},{"uid":"949f24ff-8"},{"uid":"949f24ff-18"},{"uid":"949f24ff-20"}]},"949f24ff-28":{"id":"node:module","moduleParts":{},"imported":[],"importedBy":[{"uid":"949f24ff-4"}]},"949f24ff-29":{"id":"node:url","moduleParts":{},"imported":[],"importedBy":[{"uid":"949f24ff-4"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"5a08f146-1","name":"defer-inline.ts"},{"uid":"5a08f146-3","name":"event-names.ts"},{"uid":"5a08f146-5","name":"load-native.ts"},{"uid":"5a08f146-7","name":"jsx.ts"},{"uid":"5a08f146-9","name":"pyreon-intercept.ts"},{"uid":"5a08f146-11","name":"reactivity-lens.ts"},{"uid":"5a08f146-13","name":"project-scanner.ts"},{"uid":"5a08f146-15","name":"react-intercept.ts"},{"uid":"5a08f146-17","name":"test-audit.ts"},{"uid":"5a08f146-19","name":"island-audit.ts"},{"uid":"5a08f146-21","name":"ssg-audit.ts"},{"uid":"5a08f146-23","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"5a08f146-1":{"renderedLength":15789,"gzipLength":5000,"brotliLength":0,"metaUid":"5a08f146-0"},"5a08f146-3":{"renderedLength":2941,"gzipLength":1335,"brotliLength":0,"metaUid":"5a08f146-2"},"5a08f146-5":{"renderedLength":3959,"gzipLength":1744,"brotliLength":0,"metaUid":"5a08f146-4"},"5a08f146-7":{"renderedLength":63671,"gzipLength":15676,"brotliLength":0,"metaUid":"5a08f146-6"},"5a08f146-9":{"renderedLength":29316,"gzipLength":9273,"brotliLength":0,"metaUid":"5a08f146-8"},"5a08f146-11":{"renderedLength":4373,"gzipLength":2130,"brotliLength":0,"metaUid":"5a08f146-10"},"5a08f146-13":{"renderedLength":4762,"gzipLength":1730,"brotliLength":0,"metaUid":"5a08f146-12"},"5a08f146-15":{"renderedLength":28896,"gzipLength":7322,"brotliLength":0,"metaUid":"5a08f146-14"},"5a08f146-17":{"renderedLength":13167,"gzipLength":5060,"brotliLength":0,"metaUid":"5a08f146-16"},"5a08f146-19":{"renderedLength":18208,"gzipLength":6051,"brotliLength":0,"metaUid":"5a08f146-18"},"5a08f146-21":{"renderedLength":12753,"gzipLength":4173,"brotliLength":0,"metaUid":"5a08f146-20"},"5a08f146-23":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"5a08f146-22"}},"nodeMetas":{"5a08f146-0":{"id":"/src/defer-inline.ts","moduleParts":{"index.js":"5a08f146-1"},"imported":[{"uid":"5a08f146-24"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-2":{"id":"/src/event-names.ts","moduleParts":{"index.js":"5a08f146-3"},"imported":[],"importedBy":[{"uid":"5a08f146-6"}]},"5a08f146-4":{"id":"/src/load-native.ts","moduleParts":{"index.js":"5a08f146-5"},"imported":[{"uid":"5a08f146-29"},{"uid":"5a08f146-30"},{"uid":"5a08f146-27"}],"importedBy":[{"uid":"5a08f146-6"}]},"5a08f146-6":{"id":"/src/jsx.ts","moduleParts":{"index.js":"5a08f146-7"},"imported":[{"uid":"5a08f146-25"},{"uid":"5a08f146-24"},{"uid":"5a08f146-2"},{"uid":"5a08f146-4"}],"importedBy":[{"uid":"5a08f146-22"},{"uid":"5a08f146-10"}]},"5a08f146-8":{"id":"/src/pyreon-intercept.ts","moduleParts":{"index.js":"5a08f146-9"},"imported":[{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"},{"uid":"5a08f146-10"}]},"5a08f146-10":{"id":"/src/reactivity-lens.ts","moduleParts":{"index.js":"5a08f146-11"},"imported":[{"uid":"5a08f146-6"},{"uid":"5a08f146-8"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-12":{"id":"/src/project-scanner.ts","moduleParts":{"index.js":"5a08f146-13"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-14":{"id":"/src/react-intercept.ts","moduleParts":{"index.js":"5a08f146-15"},"imported":[{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-16":{"id":"/src/test-audit.ts","moduleParts":{"index.js":"5a08f146-17"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-18":{"id":"/src/island-audit.ts","moduleParts":{"index.js":"5a08f146-19"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"},{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-20":{"id":"/src/ssg-audit.ts","moduleParts":{"index.js":"5a08f146-21"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"},{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-22":{"id":"/src/index.ts","moduleParts":{"index.js":"5a08f146-23"},"imported":[{"uid":"5a08f146-0"},{"uid":"5a08f146-6"},{"uid":"5a08f146-10"},{"uid":"5a08f146-12"},{"uid":"5a08f146-14"},{"uid":"5a08f146-8"},{"uid":"5a08f146-16"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"}],"importedBy":[],"isEntry":true},"5a08f146-24":{"id":"oxc-parser","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-0"},{"uid":"5a08f146-6"}]},"5a08f146-25":{"id":"magic-string","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-6"}]},"5a08f146-26":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-12"},{"uid":"5a08f146-16"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"}]},"5a08f146-27":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-12"},{"uid":"5a08f146-16"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"},{"uid":"5a08f146-4"}]},"5a08f146-28":{"id":"typescript","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-14"},{"uid":"5a08f146-8"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"}]},"5a08f146-29":{"id":"node:module","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-4"}]},"5a08f146-30":{"id":"node:url","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-4"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { parseSync } from "oxc-parser";
2
+ import MagicString from "magic-string";
2
3
  import { createRequire } from "node:module";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import * as path from "node:path";
@@ -657,6 +658,21 @@ const DELEGATED_EVENTS = new Set([
657
658
  "touchmove",
658
659
  "submit"
659
660
  ]);
661
+ /**
662
+ * Canonical key for a collapsible rocketstyle call site. The Vite plugin
663
+ * computes this when it resolves a site; the compiler recomputes the
664
+ * IDENTICAL key from the JSX node to look the resolution up. Stable
665
+ * ordering of props so attribute order in source doesn't change the key.
666
+ */
667
+ function rocketstyleCollapseKey(componentName, props, childrenText) {
668
+ const src = `${componentName}\u0000${Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join("")}\u0000${childrenText}`;
669
+ let h = 2166136261;
670
+ for (let i = 0; i < src.length; i++) {
671
+ h ^= src.charCodeAt(i);
672
+ h = Math.imul(h, 16777619);
673
+ }
674
+ return (h >>> 0).toString(36);
675
+ }
660
676
  function getLang(filename) {
661
677
  if (filename.endsWith(".jsx")) return "jsx";
662
678
  return "tsx";
@@ -709,7 +725,163 @@ function jsxAttrs(node) {
709
725
  function jsxChildren(node) {
710
726
  return node.children ?? [];
711
727
  }
728
+ /**
729
+ * Build a `localName → { imported, source }` table from a module's
730
+ * import declarations. Only named imports (`import { X as Y }`) are
731
+ * relevant — the collapsible components are always named exports.
732
+ */
733
+ function collectImportTable(program) {
734
+ const table = /* @__PURE__ */ new Map();
735
+ for (const stmt of program.body ?? []) {
736
+ if (stmt.type !== "ImportDeclaration") continue;
737
+ const source = stmt.source?.value;
738
+ if (typeof source !== "string") continue;
739
+ for (const spec of stmt.specifiers ?? []) {
740
+ if (spec.type !== "ImportSpecifier") continue;
741
+ const local = spec.local?.name;
742
+ const imported = spec.imported?.name ?? local;
743
+ if (typeof local === "string") table.set(local, {
744
+ imported,
745
+ source
746
+ });
747
+ }
748
+ }
749
+ return table;
750
+ }
751
+ /**
752
+ * Pure detector — finds every collapsible rocketstyle call site in a
753
+ * module. Used by `@pyreon/vite-plugin` to know which (component, props,
754
+ * text) tuples to SSR-resolve. The bail catalogue here MUST stay
755
+ * byte-identical to `tryRocketstyleCollapse`'s (RFC decision 3): a
756
+ * candidate PascalCase tag whose import source is in `collapsibleSources`,
757
+ * every attr a plain string literal (no spread, no `{expr}`, no boolean
758
+ * attr), children empty or static text only. A consistency test asserts
759
+ * the keys this produces equal the keys the compiler looks up.
760
+ */
761
+ function scanCollapsibleSites(code, filename, collapsibleSources) {
762
+ let program;
763
+ try {
764
+ program = parseSync(filename, code, {
765
+ sourceType: "module",
766
+ lang: getLang(filename)
767
+ }).program;
768
+ } catch {
769
+ return [];
770
+ }
771
+ const imports = collectImportTable(program);
772
+ const out = [];
773
+ const visit = (node) => {
774
+ if (!node || typeof node !== "object") return;
775
+ if (node.type === "JSXElement") {
776
+ const tag = jsxTagName(node);
777
+ const imp = tag ? imports.get(tag) : void 0;
778
+ if (tag && tag.charAt(0) !== tag.charAt(0).toLowerCase() && imp && collapsibleSources.has(imp.source)) {
779
+ const site = detectCollapsibleShape(node, tag);
780
+ if (site) out.push({
781
+ componentName: tag,
782
+ source: imp.source,
783
+ importedName: imp.imported,
784
+ props: site.props,
785
+ childrenText: site.childrenText,
786
+ key: rocketstyleCollapseKey(tag, site.props, site.childrenText)
787
+ });
788
+ }
789
+ }
790
+ for (const k in node) {
791
+ const v = node[k];
792
+ if (Array.isArray(v)) for (const c of v) visit(c);
793
+ else if (v && typeof v === "object" && typeof v.type === "string") visit(v);
794
+ }
795
+ };
796
+ visit(program);
797
+ return out;
798
+ }
799
+ /**
800
+ * The shared bail catalogue — every attr a string literal (no spread, no
801
+ * `{expr}`, no boolean attr), children empty or static text. Returns the
802
+ * extracted {props, childrenText} or null (bail). `tryRocketstyleCollapse`
803
+ * inlines the identical checks; a consistency test locks them together.
804
+ */
805
+ function detectCollapsibleShape(node, _tag) {
806
+ const props = {};
807
+ for (const attr of jsxAttrs(node)) {
808
+ if (attr.type !== "JSXAttribute") return null;
809
+ const nm = attr.name?.type === "JSXIdentifier" ? attr.name.name : null;
810
+ if (!nm) return null;
811
+ const v = attr.value;
812
+ if (!v) return null;
813
+ if (!(v.type === "StringLiteral" || v.type === "Literal" && typeof v.value === "string")) return null;
814
+ props[nm] = String(v.value);
815
+ }
816
+ let childrenText = "";
817
+ for (const c of jsxChildren(node)) if (c.type === "JSXText") childrenText += c.value ?? "";
818
+ else return null;
819
+ return {
820
+ props,
821
+ childrenText: childrenText.trim()
822
+ };
823
+ }
824
+ /**
825
+ * Partial-collapse detector — PR 1 of the partial-collapse spec
826
+ * (`.claude/plans/open-work-2026-q3.md` → #1). The `on*`-handler-only
827
+ * subset the bail-reason census measured at 7.8% of all
828
+ * `@pyreon/ui-components` call sites (`collapse-bail-census.test.ts`).
829
+ *
830
+ * It is the EXACT `detectCollapsibleShape` bail catalogue with ONE
831
+ * relaxation: a `{expr}`-valued attribute whose name matches `on[A-Z]…`
832
+ * (an event handler) does NOT bail — it is peeled into `handlers[]`
833
+ * instead. Handlers are orthogonal to the SSR-resolved styler class (an
834
+ * event binding never changes rendered CSS), so the literal-prop subset
835
+ * still feeds the UNCHANGED `rocketstyleCollapseKey` and the resolver's
836
+ * pre-resolved `templateHtml` / `lightClass` / `darkClass` are
837
+ * byte-identical to a full-collapse site's. The collapsed runtime node
838
+ * just re-attaches the residual handlers (PR 2 — `_rsCollapseH`).
839
+ *
840
+ * Every OTHER non-literal shape still bails (spread, non-handler
841
+ * `{expr}` prop, boolean attr, element/expression child) — conservative
842
+ * by construction, exactly like the full detector. Returns `null` when
843
+ * there are ZERO handlers so the full-collapse path stays byte-unchanged
844
+ * and the two detectors never both claim the same site (full-collapse
845
+ * sites have no handlers; partial sites have ≥1). A consistency test
846
+ * will lock this catalogue against the plugin scan in PR 3, mirroring
847
+ * the existing `detectCollapsibleShape` ↔ `scanCollapsibleSites`
848
+ * invariant — keys cannot drift.
849
+ */
850
+ function detectPartialCollapsibleShape(node, _tag) {
851
+ const props = {};
852
+ const handlers = [];
853
+ for (const attr of jsxAttrs(node)) {
854
+ if (attr.type !== "JSXAttribute") return null;
855
+ const nm = attr.name?.type === "JSXIdentifier" ? attr.name.name : null;
856
+ if (!nm) return null;
857
+ const v = attr.value;
858
+ if (!v) return null;
859
+ if (v.type === "StringLiteral" || v.type === "Literal" && typeof v.value === "string") {
860
+ props[nm] = String(v.value);
861
+ continue;
862
+ }
863
+ if (/^on[A-Z]/.test(nm) && v.type === "JSXExpressionContainer" && v.expression && typeof v.expression.start === "number" && typeof v.expression.end === "number") {
864
+ handlers.push({
865
+ name: nm,
866
+ exprStart: v.expression.start,
867
+ exprEnd: v.expression.end
868
+ });
869
+ continue;
870
+ }
871
+ return null;
872
+ }
873
+ let childrenText = "";
874
+ for (const c of jsxChildren(node)) if (c.type === "JSXText") childrenText += c.value ?? "";
875
+ else return null;
876
+ if (handlers.length === 0) return null;
877
+ return {
878
+ props,
879
+ childrenText: childrenText.trim(),
880
+ handlers
881
+ };
882
+ }
712
883
  function transformJSX(code, filename = "input.tsx", options = {}) {
884
+ if (options.collapseRocketstyle) return transformJSX_JS(code, filename, options);
713
885
  if (nativeTransformJsx) try {
714
886
  return nativeTransformJsx(code, filename, options.ssr === true, options.knownSignals ?? null, options.reactivityLens === true);
715
887
  } catch {}
@@ -801,6 +973,102 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
801
973
  let needsBindImportGlobal = false;
802
974
  let needsApplyPropsImportGlobal = false;
803
975
  let needsMountSlotImportGlobal = false;
976
+ let needsCollapse = false;
977
+ let needsCollapseH = false;
978
+ const collapseRuleKeys = /* @__PURE__ */ new Set();
979
+ const collapseRules = [];
980
+ /**
981
+ * Detect + collapse a literal-prop rocketstyle call site. Conservative
982
+ * bail catalogue (RFC decision 3): PascalCase candidate, every attr a
983
+ * StringLiteral (no spread, no `{expr}`, no boolean attr), children
984
+ * empty or a single static JSXText. The plugin must already have
985
+ * SSR-resolved this exact (component, props, text) tuple — an absent
986
+ * `sites` entry is a hard bail (covers resolver-bailed shapes,
987
+ * cross-package-without-data, anything uncertain). Emits ONE
988
+ * `_rsCollapse(tpl, light, dark, () => mode()==='dark')` (dual-emit)
989
+ * plus a once-per-module idempotent `injectRules`. A false negative is
990
+ * correct-but-slow; a false positive is wrong output — so every
991
+ * uncertain signal returns false.
992
+ */
993
+ function tryRocketstyleCollapse(node) {
994
+ const cfg = options.collapseRocketstyle;
995
+ if (!cfg) return false;
996
+ const tag = jsxTagName(node);
997
+ if (!tag || tag.charAt(0) === tag.charAt(0).toLowerCase()) return false;
998
+ if (!cfg.candidates.has(tag)) return false;
999
+ const shape = detectCollapsibleShape(node, tag);
1000
+ if (!shape) return tryPartialCollapse(node, tag);
1001
+ const { props, childrenText } = shape;
1002
+ const key = rocketstyleCollapseKey(tag, props, childrenText);
1003
+ const site = cfg.sites.get(key);
1004
+ if (!site) return false;
1005
+ const call = `__rsCollapse(${JSON.stringify(site.templateHtml)}, ${JSON.stringify(site.lightClass)}, ${JSON.stringify(site.darkClass)}, () => __pyrMode() === "dark")`;
1006
+ const start = node.start;
1007
+ const end = node.end;
1008
+ const parent = findParent(node);
1009
+ const needsBraces = parent && (parent.type === "JSXElement" || parent.type === "JSXFragment");
1010
+ replacements.push({
1011
+ start,
1012
+ end,
1013
+ text: needsBraces ? `{${call}}` : call
1014
+ });
1015
+ needsCollapse = true;
1016
+ if (!collapseRuleKeys.has(site.ruleKey)) {
1017
+ collapseRuleKeys.add(site.ruleKey);
1018
+ collapseRules.push({
1019
+ ruleKey: site.ruleKey,
1020
+ rules: site.rules
1021
+ });
1022
+ }
1023
+ return true;
1024
+ }
1025
+ /**
1026
+ * PR 3 of the partial-collapse build (open-work #1). The `on*`-handler-
1027
+ * only fallback `tryRocketstyleCollapse` defers to when the FULL
1028
+ * `detectCollapsibleShape` bails. Identical site-resolution contract as
1029
+ * the full path — handlers are orthogonal to the SSR-resolved styler
1030
+ * class, so the literal-prop subset feeds the UNCHANGED
1031
+ * `rocketstyleCollapseKey` and the resolver's pre-resolved
1032
+ * `templateHtml`/`lightClass`/`darkClass` are byte-identical to a
1033
+ * full-collapse site's. The ONLY difference vs the full emit is
1034
+ * `__rsCollapseH(...)` with a handlers object literal built from the
1035
+ * sliced source spans `detectPartialCollapsibleShape` (PR 1) returned;
1036
+ * the runtime helper (`_rsCollapseH`, PR 2 / #681) re-attaches them
1037
+ * through the canonical event path. Same conservative discipline: an
1038
+ * unresolved key, the option absent, or any non-handler non-literal
1039
+ * shape ⇒ keep the normal mount (return false).
1040
+ */
1041
+ function tryPartialCollapse(node, tag) {
1042
+ const cfg = options.collapseRocketstyle;
1043
+ if (!cfg) return false;
1044
+ const partial = detectPartialCollapsibleShape(node, tag);
1045
+ if (!partial) return false;
1046
+ const { props, childrenText, handlers } = partial;
1047
+ const key = rocketstyleCollapseKey(tag, props, childrenText);
1048
+ const site = cfg.sites.get(key);
1049
+ if (!site) return false;
1050
+ const handlerObj = `{ ${handlers.map((h) => `${JSON.stringify(h.name)}: (${code.slice(h.exprStart, h.exprEnd)})`).join(", ")} }`;
1051
+ const call = `__rsCollapseH(${JSON.stringify(site.templateHtml)}, ${JSON.stringify(site.lightClass)}, ${JSON.stringify(site.darkClass)}, () => __pyrMode() === "dark", ${handlerObj})`;
1052
+ const start = node.start;
1053
+ const end = node.end;
1054
+ const parent = findParent(node);
1055
+ const needsBraces = parent && (parent.type === "JSXElement" || parent.type === "JSXFragment");
1056
+ replacements.push({
1057
+ start,
1058
+ end,
1059
+ text: needsBraces ? `{${call}}` : call
1060
+ });
1061
+ needsCollapse = true;
1062
+ needsCollapseH = true;
1063
+ if (!collapseRuleKeys.has(site.ruleKey)) {
1064
+ collapseRuleKeys.add(site.ruleKey);
1065
+ collapseRules.push({
1066
+ ruleKey: site.ruleKey,
1067
+ rules: site.rules
1068
+ });
1069
+ }
1070
+ return true;
1071
+ }
804
1072
  function maybeHoist(node) {
805
1073
  if ((node.type === "JSXElement" || node.type === "JSXFragment") && isStaticJSXNode(node)) {
806
1074
  const name = `_$h${hoistIdx++}`;
@@ -945,6 +1213,7 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
945
1213
  }
946
1214
  const propsNames = /* @__PURE__ */ new Set();
947
1215
  const propDerivedVars = /* @__PURE__ */ new Map();
1216
+ const elementVars = /* @__PURE__ */ new Set();
948
1217
  const signalVars = new Set(options.knownSignals);
949
1218
  const shadowedSignals = /* @__PURE__ */ new Set();
950
1219
  /** Check if an identifier name is an active (non-shadowed) signal variable. */
@@ -1010,6 +1279,11 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1010
1279
  for (const el of decl.id.elements ?? []) if (el?.type === "Identifier") propsNames.add(el.name);
1011
1280
  }
1012
1281
  }
1282
+ if ((node.kind === "const" || node.kind === "let") && decl.id?.type === "Identifier" && decl.init) {
1283
+ let initNode = decl.init;
1284
+ while (initNode?.type === "ParenthesizedExpression") initNode = initNode.expression;
1285
+ if (initNode?.type === "JSXElement" || initNode?.type === "JSXFragment") elementVars.add(decl.id.name);
1286
+ }
1013
1287
  if (node.kind !== "const") continue;
1014
1288
  if (callbackDepth > 0) continue;
1015
1289
  if (decl.id?.type === "Identifier" && decl.init) {
@@ -1070,11 +1344,68 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1070
1344
  function resolveIdentifiersInText(text, baseOffset, sourceNode) {
1071
1345
  const endOffset = baseOffset + text.length;
1072
1346
  const idents = [];
1347
+ const shadowed = /* @__PURE__ */ new Set();
1348
+ /** Collect identifier names bound by a pattern (params / declarators). */
1349
+ function patternBindingNames(p, out) {
1350
+ if (!p) return;
1351
+ switch (p.type) {
1352
+ case "Identifier":
1353
+ out.push(p.name);
1354
+ break;
1355
+ case "ObjectPattern":
1356
+ for (const pr of p.properties ?? []) if (pr.type === "RestElement") patternBindingNames(pr.argument, out);
1357
+ else patternBindingNames(pr.value ?? pr.key, out);
1358
+ break;
1359
+ case "ArrayPattern":
1360
+ for (const el of p.elements ?? []) patternBindingNames(el, out);
1361
+ break;
1362
+ case "AssignmentPattern":
1363
+ patternBindingNames(p.left, out);
1364
+ break;
1365
+ case "RestElement":
1366
+ patternBindingNames(p.argument, out);
1367
+ break;
1368
+ }
1369
+ }
1370
+ /**
1371
+ * Prop-derived names bound by `node` FOR ITS OWN SUBTREE (block-accurate
1372
+ * lexical scoping). Excludes the prop-derived const's own defining
1373
+ * declaration (matched by init span) so the binding we inline FROM is
1374
+ * never mistaken for a shadow of itself.
1375
+ */
1376
+ function scopeBoundPropDerived(node) {
1377
+ const out = [];
1378
+ const t = node.type;
1379
+ const declNames = (declNode) => {
1380
+ for (const d of declNode.declarations ?? []) {
1381
+ if (d.id?.type === "Identifier" && propDerivedVars.has(d.id.name)) {
1382
+ const span = propDerivedVars.get(d.id.name);
1383
+ if (d.init && d.init.start === span.start) continue;
1384
+ }
1385
+ patternBindingNames(d.id, out);
1386
+ }
1387
+ };
1388
+ if (t === "ArrowFunctionExpression" || t === "FunctionExpression" || t === "FunctionDeclaration") for (const p of node.params ?? []) patternBindingNames(p, out);
1389
+ else if (t === "CatchClause") patternBindingNames(node.param, out);
1390
+ else if (t === "ForStatement") {
1391
+ if (node.init?.type === "VariableDeclaration") declNames(node.init);
1392
+ } else if (t === "ForInStatement" || t === "ForOfStatement") {
1393
+ if (node.left?.type === "VariableDeclaration") declNames(node.left);
1394
+ } else if (t === "BlockStatement" || t === "Program" || t === "StaticBlock") {
1395
+ const stmts = node.body ?? node.statements;
1396
+ if (Array.isArray(stmts)) {
1397
+ for (const s of stmts) if (s.type === "VariableDeclaration") declNames(s);
1398
+ else if (s.type === "FunctionDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
1399
+ else if (s.type === "ClassDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
1400
+ }
1401
+ }
1402
+ return out.filter((n) => propDerivedVars.has(n));
1403
+ }
1073
1404
  function findIdents(node, parent) {
1074
1405
  const nodeStart = node.start;
1075
1406
  const nodeEnd = node.end;
1076
1407
  if (nodeStart >= endOffset || nodeEnd <= baseOffset) return;
1077
- if (node.type === "Identifier" && propDerivedVars.has(node.name)) {
1408
+ if (node.type === "Identifier" && propDerivedVars.has(node.name) && !shadowed.has(node.name)) {
1078
1409
  if (parent) {
1079
1410
  if (parent.type === "MemberExpression" && parent.property === node && !parent.computed) {} else if (parent.type === "VariableDeclarator" && parent.id === node) {} else if (parent.type === "Property" && parent.key === node && !parent.computed) {} else if (parent.type === "Property" && parent.shorthand) {} else if (nodeStart >= baseOffset && nodeEnd <= endOffset) idents.push({
1080
1411
  start: nodeStart,
@@ -1087,7 +1418,10 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1087
1418
  name: node.name
1088
1419
  });
1089
1420
  }
1421
+ const introduced = scopeBoundPropDerived(node).filter((n) => !shadowed.has(n));
1422
+ for (const n of introduced) shadowed.add(n);
1090
1423
  forEachChildFast(node, (child) => findIdents(child, node));
1424
+ for (const n of introduced) shadowed.delete(n);
1091
1425
  }
1092
1426
  findIdents(program, null);
1093
1427
  if (idents.length === 0) return text;
@@ -1175,6 +1509,7 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1175
1509
  }
1176
1510
  if (node.type === "VariableDeclaration") collectPropDerivedFromDecl(node, _callbackDepth);
1177
1511
  if (node.type === "JSXElement") {
1512
+ if (tryRocketstyleCollapse(node)) return;
1178
1513
  if (!isSelfClosing(node) && tryTemplateEmit(node)) return;
1179
1514
  checkForWarnings(node);
1180
1515
  for (const attr of jsxAttrs(node)) if (attr.type === "JSXAttribute") handleJsxAttribute(attr, node);
@@ -1204,16 +1539,11 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1204
1539
  warnings
1205
1540
  };
1206
1541
  replacements.sort((a, b) => a.start - b.start);
1207
- const outParts = [];
1208
- let outPos = 0;
1209
- for (const r of replacements) {
1210
- outParts.push(code.slice(outPos, r.start));
1211
- outParts.push(r.text);
1212
- outPos = r.end;
1213
- }
1214
- outParts.push(code.slice(outPos));
1215
- let output = outParts.join("");
1216
- if (hoists.length > 0) output = hoists.map((h) => `const ${h.name} = /*@__PURE__*/ ${h.text}\n`).join("") + output;
1542
+ const s = new MagicString(code);
1543
+ for (const r of replacements) if (r.start === r.end) s.appendLeft(r.start, r.text);
1544
+ else s.update(r.start, r.end, r.text);
1545
+ let preamble = "";
1546
+ if (hoists.length > 0) preamble = hoists.map((h) => `const ${h.name} = /*@__PURE__*/ ${h.text}\n`).join("") + preamble;
1217
1547
  if (needsTplImport) {
1218
1548
  const runtimeDomImports = ["_tpl"];
1219
1549
  if (needsBindDirectImportGlobal) runtimeDomImports.push("_bindDirect");
@@ -1221,23 +1551,39 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1221
1551
  if (needsApplyPropsImportGlobal) runtimeDomImports.push("_applyProps");
1222
1552
  if (needsMountSlotImportGlobal) runtimeDomImports.push("_mountSlot");
1223
1553
  const reactivityImports = needsBindImportGlobal ? `\nimport { _bind } from "@pyreon/reactivity";` : "";
1224
- output = `import { ${runtimeDomImports.join(", ")} } from "@pyreon/runtime-dom";${reactivityImports}\n` + output;
1554
+ preamble = `import { ${runtimeDomImports.join(", ")} } from "@pyreon/runtime-dom";${reactivityImports}\n` + preamble;
1225
1555
  }
1226
1556
  if (needsRpImport || needsWrapSpreadImport) {
1227
1557
  const coreImports = [];
1228
1558
  if (needsRpImport) coreImports.push("_rp");
1229
1559
  if (needsWrapSpreadImport) coreImports.push("_wrapSpread");
1230
- output = `import { ${coreImports.join(", ")} } from "@pyreon/core";\n` + output;
1231
- }
1560
+ preamble = `import { ${coreImports.join(", ")} } from "@pyreon/core";\n` + preamble;
1561
+ }
1562
+ if (needsCollapse) {
1563
+ const cfg = options.collapseRocketstyle;
1564
+ const rd = cfg.runtimeDomSource ?? "@pyreon/runtime-dom";
1565
+ const st = cfg.stylerSource ?? "@pyreon/styler";
1566
+ const inj = collapseRules.map((r) => `__rsSheet.injectRules(${JSON.stringify(r.rules)},${JSON.stringify(r.ruleKey)});`).join("");
1567
+ preamble = `import { _rsCollapse as __rsCollapse${needsCollapseH ? ", _rsCollapseH as __rsCollapseH" : ""} } from "${rd}";\nimport { sheet as __rsSheet } from "${st}";\nimport { ${cfg.mode.name} as __pyrMode } from "${cfg.mode.source}";\n${inj}\n` + preamble;
1568
+ }
1569
+ if (preamble) s.prepend(preamble);
1570
+ const output = s.toString();
1571
+ const map = s.generateMap({
1572
+ source: filename,
1573
+ includeContent: true,
1574
+ hires: true
1575
+ });
1232
1576
  return collectLens ? {
1233
1577
  code: output,
1234
1578
  usesTemplates: needsTplImport,
1235
1579
  warnings,
1580
+ map,
1236
1581
  reactivityLens
1237
1582
  } : {
1238
1583
  code: output,
1239
1584
  usesTemplates: needsTplImport,
1240
- warnings
1585
+ warnings,
1586
+ map
1241
1587
  };
1242
1588
  function hasBailAttr(node, isRoot = false) {
1243
1589
  for (const attr of jsxAttrs(node)) {
@@ -1525,7 +1871,8 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1525
1871
  }
1526
1872
  const needsPlaceholder = useMixed || useMultiExpr;
1527
1873
  const { expr, isReactive } = unwrapAccessor(child.expression);
1528
- if (isChildrenExpression(child.expression, expr)) {
1874
+ const isElementValuedIdent = child.expression?.type === "Identifier" && elementVars.has(child.expression.name) || !isReactive && /^[A-Za-z_$][\w$]*$/.test(expr) && elementVars.has(expr);
1875
+ if (isChildrenExpression(child.expression, expr) || isElementValuedIdent) {
1529
1876
  needsMountSlotImport = true;
1530
1877
  const placeholder = `${parentRef}.childNodes[${childNodeIdx}]`;
1531
1878
  const d = nextDisp();
@@ -1618,12 +1965,64 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1618
1965
  }
1619
1966
  /** Auto-insert () after signal variable references in the expression source.
1620
1967
  * Uses the AST to find exact Identifier positions — never scans raw text. */
1968
+ function sigPatternNames(p, out) {
1969
+ if (!p) return;
1970
+ switch (p.type) {
1971
+ case "Identifier":
1972
+ out.push(p.name);
1973
+ break;
1974
+ case "ObjectPattern":
1975
+ for (const pr of p.properties ?? []) if (pr.type === "RestElement") sigPatternNames(pr.argument, out);
1976
+ else sigPatternNames(pr.value ?? pr.key, out);
1977
+ break;
1978
+ case "ArrayPattern":
1979
+ for (const el of p.elements ?? []) sigPatternNames(el, out);
1980
+ break;
1981
+ case "AssignmentPattern":
1982
+ sigPatternNames(p.left, out);
1983
+ break;
1984
+ case "RestElement":
1985
+ sigPatternNames(p.argument, out);
1986
+ break;
1987
+ }
1988
+ }
1989
+ function scopeBoundSignals(node) {
1990
+ const out = [];
1991
+ const t = node.type;
1992
+ const declNames = (declNode) => {
1993
+ for (const d of declNode.declarations ?? []) {
1994
+ if (d.id?.type === "Identifier" && d.init && isSignalCall(d.init)) continue;
1995
+ sigPatternNames(d.id, out);
1996
+ }
1997
+ };
1998
+ if (t === "ArrowFunctionExpression" || t === "FunctionExpression" || t === "FunctionDeclaration") for (const p of node.params ?? []) sigPatternNames(p, out);
1999
+ else if (t === "CatchClause") sigPatternNames(node.param, out);
2000
+ else if (t === "ForStatement") {
2001
+ if (node.init?.type === "VariableDeclaration") declNames(node.init);
2002
+ } else if (t === "ForInStatement" || t === "ForOfStatement") {
2003
+ if (node.left?.type === "VariableDeclaration") declNames(node.left);
2004
+ } else if (t === "BlockStatement" || t === "StaticBlock") {
2005
+ const stmts = node.body ?? node.statements;
2006
+ if (Array.isArray(stmts)) {
2007
+ for (const s of stmts) if (s.type === "VariableDeclaration") declNames(s);
2008
+ else if (s.type === "FunctionDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
2009
+ else if (s.type === "ClassDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
2010
+ }
2011
+ }
2012
+ return out.filter((n) => signalVars.has(n));
2013
+ }
1621
2014
  function autoCallSignals(text, expr) {
1622
2015
  const start = expr.start;
1623
2016
  const idents = [];
2017
+ const shadowed = /* @__PURE__ */ new Set();
1624
2018
  function findSignalIdents(node) {
1625
2019
  if (node.start >= start + text.length || node.end <= start) return;
1626
- if (node.type === "Identifier" && isActiveSignal(node.name)) {
2020
+ const introduced = [];
2021
+ for (const n of scopeBoundSignals(node)) if (!shadowed.has(n)) {
2022
+ shadowed.add(n);
2023
+ introduced.push(n);
2024
+ }
2025
+ if (node.type === "Identifier" && isActiveSignal(node.name) && !shadowed.has(node.name)) {
1627
2026
  const parent = findParent(node);
1628
2027
  if (parent && parent.type === "MemberExpression" && parent.property === node && !parent.computed) return;
1629
2028
  if (parent && parent.type === "MemberExpression" && parent.object === node) {
@@ -1642,6 +2041,7 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
1642
2041
  });
1643
2042
  }
1644
2043
  forEachChildFast(node, findSignalIdents);
2044
+ for (const n of introduced) shadowed.delete(n);
1645
2045
  }
1646
2046
  findSignalIdents(expr);
1647
2047
  if (idents.length === 0) return text;
@@ -4438,5 +4838,5 @@ function formatSsgAudit(result, _options = {}) {
4438
4838
  }
4439
4839
 
4440
4840
  //#endregion
4441
- export { analyzeReactivity, auditIslands, auditSsg, auditTestEnvironment, detectPyreonPatterns, detectReactPatterns, diagnoseError, formatIslandAudit, formatReactivityLens, formatSsgAudit, formatTestAudit, generateContext, hasPyreonPatterns, hasReactPatterns, migrateReactCode, transformDeferInline, transformJSX, transformJSX_JS };
4841
+ export { analyzeReactivity, auditIslands, auditSsg, auditTestEnvironment, detectPyreonPatterns, detectReactPatterns, diagnoseError, formatIslandAudit, formatReactivityLens, formatSsgAudit, formatTestAudit, generateContext, hasPyreonPatterns, hasReactPatterns, migrateReactCode, rocketstyleCollapseKey, scanCollapsibleSites, transformDeferInline, transformJSX, transformJSX_JS };
4442
4842
  //# sourceMappingURL=index.js.map