elcrm 1.1.2 → 1.1.4

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 (41) hide show
  1. package/dist/index.js +696 -48
  2. package/dist/vite/concat-css.js +13 -1
  3. package/dist/vite/postbuild.js +13 -1
  4. package/package.json +4 -3
  5. package/templates/elcrm-cursor/AGENTS.md +3 -1
  6. package/templates/elcrm-cursor/rules/elcrm-jsdoc.mdc +89 -0
  7. package/templates/elcrm-cursor/rules/elcrm-lib.mdc +1 -0
  8. package/templates/elcrm-cursor/rules/elcrm-packages.mdc +2 -2
  9. package/templates/elcrm-cursor/rules/elcrm-theme.mdc +1 -0
  10. package/templates/elcrm-cursor/rules/elcrm-tokens.mdc +40 -0
  11. package/templates/elcrm-cursor/rules/elcrm-ui.mdc +1 -0
  12. package/templates/elcrm-cursor/rules/elcrm.mdc +1 -1
  13. package/templates/elcrm-docs/CLI.elCRM.md +3 -3
  14. package/templates/elcrm-docs/COMPONENTS.elCRM.md +51 -46
  15. package/templates/panel/web/src/App.tsx +15 -1
  16. package/templates/panel/web/src/component/Header.tsx +19 -6
  17. package/templates/panel/web/src/lazyRoute.ts +6 -0
  18. package/templates/panel/web/src/lib/routeKey.ts +8 -1
  19. package/templates/panel/web/src/lib/theme.ts +20 -3
  20. package/templates/panel/web/src/modules/Account/index.tsx +12 -8
  21. package/templates/panel/web/src/modules/Account/router.sections.ts +5 -3
  22. package/templates/panel/web/src/modules/Account/sections/Profile.tsx +9 -4
  23. package/templates/panel/web/src/modules/Account/sections/Sessions.tsx +1 -1
  24. package/templates/panel/web/src/modules/Home/index.tsx +11 -4
  25. package/templates/panel/web/src/modules/Home/modal/Demo.tsx +10 -0
  26. package/templates/panel/web/src/modules/List/index.tsx +12 -11
  27. package/templates/panel/web/src/modules/List/router.sections.ts +6 -4
  28. package/templates/panel/web/src/modules/List/sections/Basic.tsx +3 -3
  29. package/templates/panel/web/src/modules/List/sections/Horizontal.tsx +1 -1
  30. package/templates/panel/web/src/modules/List/sections/Rows.tsx +6 -3
  31. package/templates/panel/web/src/modules/Settings/index.tsx +14 -8
  32. package/templates/panel/web/src/modules/Settings/router.sections.ts +7 -4
  33. package/templates/panel/web/src/modules/Settings/sections/Appearance.tsx +8 -4
  34. package/templates/panel/web/src/modules/Settings/sections/General.tsx +5 -1
  35. package/templates/panel/web/src/modules/Settings/sections/Profile.tsx +5 -1
  36. package/templates/panel/web/src/pages/Auth.tsx +10 -0
  37. package/templates/panel/web/src/pages/Main.tsx +7 -1
  38. package/templates/panel/web/src/state.ts +9 -0
  39. package/templates/panel/web/src/style/theme-dark.css +18 -87
  40. package/templates/panel/web/src/style/theme-light.css +18 -87
  41. package/templates/panel/web/src/style/theme.css +29 -78
package/dist/index.js CHANGED
@@ -533,19 +533,112 @@ var init_form_aliases = __esm(() => {
533
533
  ];
534
534
  });
535
535
 
536
- // src/lib/migrate/form-tokens.ts
537
- import { readFileSync as readFileSync9 } from "fs";
538
- function removeDecl(css, name) {
539
- const re = new RegExp(`\\n?[ \\t]*${name.replace(/-/g, "\\-")}\\s*:[^;\\n}]+;?[ \\t]*`, "g");
536
+ // src/lib/migrate/css-decl.ts
537
+ function removeCssDecl(css, name) {
538
+ const re = new RegExp(`\\n?[ \\t]*${name.replace(/-/g, "\\-")}(?![\\w-])\\s*:`, "g");
540
539
  let n = 0;
541
- const next = css.replace(re, () => {
540
+ const ranges = [];
541
+ let m;
542
+ while ((m = re.exec(css)) !== null) {
543
+ const start = m.index;
544
+ let i = m.index + m[0].length;
545
+ let depth = 0;
546
+ while (i < css.length) {
547
+ const ch = css[i];
548
+ if (ch === "(")
549
+ depth++;
550
+ else if (ch === ")")
551
+ depth = Math.max(0, depth - 1);
552
+ else if ((ch === ";" || ch === "}") && depth === 0) {
553
+ if (ch === ";")
554
+ i++;
555
+ break;
556
+ }
557
+ i++;
558
+ }
559
+ let s = start;
560
+ if (s > 0 && css[s - 1] === `
561
+ `)
562
+ s--;
563
+ ranges.push({ start: s, end: i });
542
564
  n++;
543
- return "";
544
- });
565
+ }
566
+ if (!ranges.length)
567
+ return { next: css, n: 0 };
568
+ let next = css;
569
+ for (let r = ranges.length - 1;r >= 0; r--) {
570
+ const { start, end } = ranges[r];
571
+ next = next.slice(0, start) + next.slice(end);
572
+ }
545
573
  return { next: next.replace(/\n{3,}/g, `
546
574
 
547
575
  `), n };
548
576
  }
577
+
578
+ // src/lib/migrate/css-sanitize.ts
579
+ import { readFileSync as readFileSync9 } from "fs";
580
+ function stripOrphanColorMix(source) {
581
+ let next = source;
582
+ let hits = 0;
583
+ next = next.replace(ORPHAN_MIX_BLOCK_RE, () => {
584
+ hits++;
585
+ return `
586
+ `;
587
+ });
588
+ const lines = next.split(/\r?\n/);
589
+ const kept = [];
590
+ for (const line of lines) {
591
+ if (/^[ \t]*in\s+(?:srgb|hsl|hwb|lab|lch)\s*,?\s*$/i.test(line) || /^[ \t]*transparent\s*\)\s*;?\s*$/i.test(line) || /^[ \t]*var\([^)]+\)\s*\d+%\s*,?\s*$/i.test(line)) {
592
+ const prev = kept[kept.length - 1] ?? "";
593
+ if (!/color-mix\s*\(\s*$/i.test(prev) && !/color-mix\s*\(/i.test(prev)) {
594
+ hits++;
595
+ continue;
596
+ }
597
+ }
598
+ kept.push(line);
599
+ }
600
+ next = kept.join(`
601
+ `).replace(/\n{3,}/g, `
602
+
603
+ `);
604
+ return { next, hits };
605
+ }
606
+ function lineHasOrphanColorMix(line) {
607
+ const t = line.trim();
608
+ if (!t || t.startsWith("/*") || t.startsWith("*"))
609
+ return false;
610
+ if (/^in\s+(?:srgb|hsl|hwb|lab|lch)\b/i.test(t) && !/color-mix\s*\(/i.test(t)) {
611
+ return true;
612
+ }
613
+ if (/^transparent\s*\)\s*;?\s*$/i.test(t))
614
+ return true;
615
+ return false;
616
+ }
617
+ async function migrateCssSanitize(cwd, dry) {
618
+ const changes = [];
619
+ const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => f.endsWith(".css"));
620
+ for (const file of files) {
621
+ const raw = readFileSync9(file, "utf8");
622
+ const { next, hits } = stripOrphanColorMix(raw);
623
+ if (hits === 0 || next === raw)
624
+ continue;
625
+ if (writeIfChanged(file, next, dry) || dry) {
626
+ changes.push({
627
+ file: rel(cwd, file),
628
+ detail: `${hits}\xD7 \u0443\u0434\u0430\u043B\u0435\u043D\u044B \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0435 color-mix/\u0444\u0440\u0430\u0433\u043C\u0435\u043D\u0442\u044B`
629
+ });
630
+ }
631
+ }
632
+ return changes;
633
+ }
634
+ var ORPHAN_MIX_BLOCK_RE;
635
+ var init_css_sanitize = __esm(() => {
636
+ init_walk();
637
+ ORPHAN_MIX_BLOCK_RE = /(?:^|\n)[ \t]*(?:in\s+srgb|in\s+hsl|in\s+hwb|in\s+lab|in\s+lch)[\s\S]*?\n[ \t]*\);?[ \t]*(?=\n|$)/gi;
638
+ });
639
+
640
+ // src/lib/migrate/form-tokens.ts
641
+ import { readFileSync as readFileSync10 } from "fs";
549
642
  function migrateDatePopupShadowDecl(css) {
550
643
  let n = 0;
551
644
  let next = css;
@@ -600,10 +693,13 @@ function rewriteFormTokens(source) {
600
693
  const stillUsed = new RegExp(`var\\(\\s*${name.replace(/-/g, "\\-")}\\s*`, "i").test(next);
601
694
  if (stillUsed)
602
695
  continue;
603
- const rem = removeDecl(next, name);
696
+ const rem = removeCssDecl(next, name);
604
697
  next = rem.next;
605
698
  hits += rem.n;
606
699
  }
700
+ const clean = stripOrphanColorMix(next);
701
+ next = clean.next;
702
+ hits += clean.hits;
607
703
  return { next, hits };
608
704
  }
609
705
  async function migrateFormTokens(cwd, dry) {
@@ -613,7 +709,7 @@ async function migrateFormTokens(cwd, dry) {
613
709
  const changes = [];
614
710
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => f.endsWith(".css"));
615
711
  for (const file of files) {
616
- const raw = readFileSync9(file, "utf8");
712
+ const raw = readFileSync10(file, "utf8");
617
713
  const { next, hits } = rewriteFormTokens(raw);
618
714
  if (hits === 0 || next === raw)
619
715
  continue;
@@ -642,6 +738,7 @@ var VAR_REPLACEMENTS, DECL_REMOVE, FORM_TOKEN_DEPRECATED, DEPRECATED_VAR_RE, DEP
642
738
  var init_form_tokens = __esm(() => {
643
739
  init_pkg();
644
740
  init_walk();
741
+ init_css_sanitize();
645
742
  VAR_REPLACEMENTS = [
646
743
  [
647
744
  /var\(\s*--field-note-padding-top\s*(?:,[^)]+)?\)/gi,
@@ -688,6 +785,392 @@ var init_form_tokens = __esm(() => {
688
785
  DEPRECATED_DECL_RE = new RegExp(`(?:${FORM_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})\\s*:`);
689
786
  });
690
787
 
788
+ // src/lib/migrate/components.ts
789
+ import { readFileSync as readFileSync11 } from "fs";
790
+ function rewriteComponentsCss(source) {
791
+ let next = source;
792
+ let hits = 0;
793
+ for (const [re, to] of VAR_REPLACEMENTS2) {
794
+ next = next.replace(re, () => {
795
+ hits++;
796
+ return to;
797
+ });
798
+ }
799
+ for (const name of COMPONENTS_TOKEN_DEPRECATED) {
800
+ const stillUsed = new RegExp(`var\\(\\s*${name.replace(/-/g, "\\-")}(?![\\w-])`, "i").test(next);
801
+ if (stillUsed)
802
+ continue;
803
+ const rem = removeCssDecl(next, name);
804
+ next = rem.next;
805
+ hits += rem.n;
806
+ }
807
+ return { next, hits };
808
+ }
809
+ function rewriteImports2(source) {
810
+ let hits = 0;
811
+ let next = source;
812
+ const replaceImportList = (list) => {
813
+ let s = list;
814
+ const before = s;
815
+ s = s.replace(/\bBlock\b/g, "Stack");
816
+ s = s.replace(/\bRow\b/g, "Stack");
817
+ s = s.replace(/\bColumn\b/g, "Stack");
818
+ const parts = s.split(",").map((p) => p.trim()).filter(Boolean);
819
+ const seen = new Set;
820
+ const uniq = [];
821
+ for (const p of parts) {
822
+ const bare = p.replace(/\s+as\s+\w+$/, "").trim();
823
+ if (seen.has(bare)) {
824
+ hits++;
825
+ continue;
826
+ }
827
+ seen.add(bare);
828
+ uniq.push(p);
829
+ }
830
+ if (s !== before || uniq.join(", ") !== list.trim())
831
+ hits++;
832
+ return uniq.join(", ");
833
+ };
834
+ next = next.replace(/import\s*\{([^}]+)\}\s*from\s*["']@elcrm\/components["']/g, (full, list) => {
835
+ if (!/\b(Block|Row|Column)\b/.test(list))
836
+ return full;
837
+ hits++;
838
+ return `import { ${replaceImportList(list)} } from "@elcrm/components"`;
839
+ });
840
+ next = next.replace(/import\s*\{\s*Block\s*\}\s*from\s*["']@elcrm\/components\/Block["']/g, () => {
841
+ hits++;
842
+ return `import { Stack } from "@elcrm/components/Stack"`;
843
+ });
844
+ next = next.replace(/import\s*\{\s*Row\s*\}\s*from\s*["']@elcrm\/components\/Row["']/g, () => {
845
+ hits++;
846
+ return `import { Stack } from "@elcrm/components/Stack"`;
847
+ });
848
+ next = next.replace(/import\s*\{\s*Column\s*\}\s*from\s*["']@elcrm\/components\/Column["']/g, () => {
849
+ hits++;
850
+ return `import { Stack } from "@elcrm/components/Stack"`;
851
+ });
852
+ return { next, hits };
853
+ }
854
+ function rewriteJsx(source) {
855
+ let hits = 0;
856
+ let next = source;
857
+ next = next.replace(/<Block([^>]*?)\srow(\s|\/|>)/g, (_m, attrs, end) => {
858
+ hits++;
859
+ return `<Stack${attrs} direction="row"${end}`;
860
+ });
861
+ next = next.replace(/<\/Block>/g, () => {
862
+ hits++;
863
+ return "</Stack>";
864
+ });
865
+ next = next.replace(/<Block(\s|\/|>)/g, (_m, end) => {
866
+ hits++;
867
+ return `<Stack${end}`;
868
+ });
869
+ next = next.replace(/<Row(\s[^/>]*(?:\/>|>))/g, (_m, rest) => {
870
+ hits++;
871
+ return `<Stack direction="row"${rest}`;
872
+ });
873
+ next = next.replace(/<\/Row>/g, () => {
874
+ hits++;
875
+ return "</Stack>";
876
+ });
877
+ next = next.replace(/<\/Column>/g, () => {
878
+ hits++;
879
+ return "</Stack>";
880
+ });
881
+ next = next.replace(/<Column(\s|\/|>)/g, (_m, end) => {
882
+ hits++;
883
+ return `<Stack${end}`;
884
+ });
885
+ next = next.replace(/\bactiveKey=/g, () => {
886
+ hits++;
887
+ return "value=";
888
+ });
889
+ next = next.replace(/\bdefaultActiveKey=/g, () => {
890
+ hits++;
891
+ return "defaultValue=";
892
+ });
893
+ return { next, hits };
894
+ }
895
+ function rewriteSectionOnChange(source) {
896
+ if (!/\b(NavSections|TabSections)\b/.test(source)) {
897
+ return { next: source, hits: 0 };
898
+ }
899
+ let hits = 0;
900
+ const next = source.replace(/\bonChange=/g, () => {
901
+ hits++;
902
+ return "onValueChange=";
903
+ });
904
+ return { next, hits };
905
+ }
906
+ function rewriteItemKeys(source) {
907
+ if (!/\b(Menu|NavSections|TabSections|MenuItem|NavSectionsItem|TabSectionsItem)\b/.test(source)) {
908
+ return { next: source, hits: 0 };
909
+ }
910
+ let hits = 0;
911
+ const next = source.replace(/(\{\s*)key(\s*:\s*["'`])/g, (_m, a, b) => {
912
+ hits++;
913
+ return `${a}value${b}`;
914
+ });
915
+ const next2 = next.replace(/(\.\s*)key(\s*(?:[,)\]]|;|\s*[=!]==))/g, (full, a, b) => {
916
+ if (/\bt\.key|\bi\.key|item\.key/.test(full) || /t\.key|i\.key/.test(a + "key")) {
917
+ hits++;
918
+ return `${a}value${b}`;
919
+ }
920
+ return full;
921
+ });
922
+ const next3 = next2.replace(/\b(t|i|item)\.key\b/g, (_m, obj) => {
923
+ hits++;
924
+ return `${obj}.value`;
925
+ });
926
+ return { next: next3, hits };
927
+ }
928
+ function rewriteComponentsTsx(source) {
929
+ let hits = 0;
930
+ let next = source;
931
+ const imp = rewriteImports2(next);
932
+ next = imp.next;
933
+ hits += imp.hits;
934
+ const jsx = rewriteJsx(next);
935
+ next = jsx.next;
936
+ hits += jsx.hits;
937
+ const oc = rewriteSectionOnChange(next);
938
+ next = oc.next;
939
+ hits += oc.hits;
940
+ const keys = rewriteItemKeys(next);
941
+ next = keys.next;
942
+ hits += keys.hits;
943
+ return { next, hits };
944
+ }
945
+ function fixStackDirectionSpacing(source) {
946
+ return source.replace(/<Stack direction="row"(\S)/g, `<Stack direction="row" $1`);
947
+ }
948
+ async function migrateComponents(cwd, dry) {
949
+ const pkg = readPackageJson(cwd);
950
+ if (pkg?.name === "@elcrm/components")
951
+ return [];
952
+ const changes = [];
953
+ const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
954
+ for (const file of files) {
955
+ const raw = readFileSync11(file, "utf8");
956
+ let next = raw;
957
+ let hits = 0;
958
+ if (file.endsWith(".css")) {
959
+ const r = rewriteComponentsCss(raw);
960
+ const clean = stripOrphanColorMix(r.next);
961
+ next = clean.next;
962
+ hits = r.hits + clean.hits;
963
+ } else if (/\.(tsx?|jsx?|mdx?)$/.test(file)) {
964
+ const r = rewriteComponentsTsx(raw);
965
+ next = fixStackDirectionSpacing(r.next);
966
+ hits = r.hits;
967
+ } else {
968
+ continue;
969
+ }
970
+ if (hits === 0 || next === raw)
971
+ continue;
972
+ if (writeIfChanged(file, next, dry) || dry) {
973
+ changes.push({
974
+ file: rel(cwd, file),
975
+ detail: `${hits}\xD7 components (Stack / value / shell-tokens)`
976
+ });
977
+ }
978
+ }
979
+ return changes;
980
+ }
981
+ function lineHasDeprecatedComponentsToken(line) {
982
+ const t = line.trim();
983
+ if (!t || t.startsWith("/*") || t.startsWith("*"))
984
+ return false;
985
+ if (DEPRECATED_VAR_RE2.test(t))
986
+ return true;
987
+ if (!DEPRECATED_DECL_RE2.test(t))
988
+ return false;
989
+ if (/:\s*var\(\s*--shell-/.test(t))
990
+ return false;
991
+ return true;
992
+ }
993
+ var VAR_REPLACEMENTS2, COMPONENTS_TOKEN_DEPRECATED, DEPRECATED_VAR_RE2, DEPRECATED_DECL_RE2;
994
+ var init_components = __esm(() => {
995
+ init_pkg();
996
+ init_walk();
997
+ init_css_sanitize();
998
+ VAR_REPLACEMENTS2 = [
999
+ [/var\(\s*--layout-background\s*(?:,[^)]+)?\)/gi, "var(--shell-background-muted)"],
1000
+ [/var\(\s*--header-background\s*(?:,[^)]+)?\)/gi, "var(--shell-background)"],
1001
+ [/var\(\s*--header-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1002
+ [/var\(\s*--header-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1003
+ [/var\(\s*--footer-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1004
+ [/var\(\s*--footer-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1005
+ [/var\(\s*--item-background-hover\s*(?:,[^)]+)?\)/gi, "var(--shell-background-hover)"],
1006
+ [/var\(\s*--item-background-active\s*(?:,[^)]+)?\)/gi, "var(--shell-background-selected)"],
1007
+ [/var\(\s*--item-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1008
+ [/var\(\s*--card-background\s*(?:,[^)]+)?\)/gi, "var(--shell-background)"],
1009
+ [/var\(\s*--card-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1010
+ [/var\(\s*--card-shadow\s*(?:,[^)]+)?\)/gi, "var(--shell-shadow)"],
1011
+ [/var\(\s*--menu-item-background-hover\s*(?:,[^)]+)?\)/gi, "var(--shell-background-hover)"],
1012
+ [/var\(\s*--menu-item-background-active\s*(?:,[^)]+)?\)/gi, "var(--shell-background-selected)"],
1013
+ [/var\(\s*--menu-item-color-active\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1014
+ [/var\(\s*--menu-item-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1015
+ [/var\(\s*--loading-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1016
+ [/var\(\s*--section-title-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1017
+ [/var\(\s*--section-description-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1018
+ [/var\(\s*--section-auth-card-background\s*(?:,[^)]+)?\)/gi, "var(--shell-background)"],
1019
+ [/var\(\s*--section-auth-card-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1020
+ [/var\(\s*--section-auth-card-shadow\s*(?:,[^)]+)?\)/gi, "var(--shell-shadow)"],
1021
+ [/var\(\s*--section-block-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1022
+ [/var\(\s*--nav-sections-label-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1023
+ [/var\(\s*--nav-sections-item-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1024
+ [/var\(\s*--nav-sections-item-background-hover\s*(?:,[^)]+)?\)/gi, "var(--shell-background-hover)"],
1025
+ [/var\(\s*--nav-sections-item-background-active\s*(?:,[^)]+)?\)/gi, "var(--shell-background-selected)"],
1026
+ [/var\(\s*--nav-sections-accent\s*(?:,[^)]+)?\)/gi, "var(--shell-color-accent)"],
1027
+ [/var\(\s*--nav-sections-icon-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1028
+ [/var\(\s*--nav-sections-group-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1029
+ [/var\(\s*--nav-sections-footer-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1030
+ [/var\(\s*--tab-sections-border\s*(?:,[^)]+)?\)/gi, "var(--shell-border)"],
1031
+ [/var\(\s*--tab-sections-color-active\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1032
+ [/var\(\s*--tab-sections-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-muted)"],
1033
+ [/var\(\s*--tab-sections-accent\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1034
+ [/var\(\s*--tab-sections-badge-background\s*(?:,[^)]+)?\)/gi, "var(--shell-color-danger)"],
1035
+ [/var\(\s*--brand-color-hover\s*(?:,[^)]+)?\)/gi, "var(--shell-color-accent)"],
1036
+ [/var\(\s*--brand-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1037
+ [/var\(\s*--badge-neutral-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color)"],
1038
+ [/var\(\s*--badge-accent-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-accent)"],
1039
+ [/var\(\s*--badge-success-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-success)"],
1040
+ [/var\(\s*--badge-danger-color\s*(?:,[^)]+)?\)/gi, "var(--shell-color-danger)"],
1041
+ [/var\(\s*--list-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap-sm)"],
1042
+ [/var\(\s*--item-padding\s*(?:,[^)]+)?\)/gi, "var(--shell-padding)"],
1043
+ [/var\(\s*--item-radius\s*(?:,[^)]+)?\)/gi, "var(--shell-radius)"],
1044
+ [/var\(\s*--block-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
1045
+ [/var\(\s*--row-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
1046
+ [/var\(\s*--column-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
1047
+ [/var\(\s*--page-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
1048
+ [/var\(\s*--page-pad\s*(?:,[^)]+)?\)/gi, "var(--shell-padding)"],
1049
+ [/var\(\s*--page-pad-x\s*(?:,[^)]+)?\)/gi, "var(--shell-padding-inline)"],
1050
+ [/var\(\s*--page-pad-y\s*(?:,[^)]+)?\)/gi, "var(--shell-padding-block)"],
1051
+ [/var\(\s*--card-radius\s*(?:,[^)]+)?\)/gi, "var(--shell-radius)"],
1052
+ [/var\(\s*--section-description-size\s*(?:,[^)]+)?\)/gi, "0.95rem"],
1053
+ [/var\(\s*--section-title-size\s*(?:,[^)]+)?\)/gi, "1.5rem"]
1054
+ ];
1055
+ COMPONENTS_TOKEN_DEPRECATED = [
1056
+ "--layout-background",
1057
+ "--layout-min-height",
1058
+ "--layout-gap",
1059
+ "--header-background",
1060
+ "--header-color",
1061
+ "--header-border",
1062
+ "--header-padding",
1063
+ "--header-gap",
1064
+ "--footer-background",
1065
+ "--footer-color",
1066
+ "--footer-border",
1067
+ "--footer-padding",
1068
+ "--footer-font-size",
1069
+ "--list-gap",
1070
+ "--list-padding",
1071
+ "--item-background",
1072
+ "--item-background-hover",
1073
+ "--item-background-active",
1074
+ "--item-color",
1075
+ "--item-color-muted",
1076
+ "--item-border",
1077
+ "--item-radius",
1078
+ "--item-padding",
1079
+ "--item-gap",
1080
+ "--card-background",
1081
+ "--card-border",
1082
+ "--card-shadow",
1083
+ "--card-radius",
1084
+ "--card-padding",
1085
+ "--block-gap",
1086
+ "--block-padding",
1087
+ "--row-gap",
1088
+ "--column-gap",
1089
+ "--menu-gap",
1090
+ "--menu-item-radius",
1091
+ "--menu-item-padding",
1092
+ "--menu-item-background",
1093
+ "--menu-item-background-hover",
1094
+ "--menu-item-background-active",
1095
+ "--menu-item-color",
1096
+ "--menu-item-color-active",
1097
+ "--loading-padding",
1098
+ "--loading-color",
1099
+ "--section-gap",
1100
+ "--section-description-size",
1101
+ "--section-icon-gap",
1102
+ "--section-body-gap",
1103
+ "--section-actions-gap",
1104
+ "--section-auth-padding",
1105
+ "--section-auth-card-padding",
1106
+ "--section-auth-card-radius",
1107
+ "--section-block-padding-bottom",
1108
+ "--section-block-margin-bottom",
1109
+ "--section-title-color",
1110
+ "--section-description-color",
1111
+ "--section-auth-card-background",
1112
+ "--section-auth-card-border",
1113
+ "--section-auth-card-shadow",
1114
+ "--section-block-border",
1115
+ "--nav-sections-gap",
1116
+ "--nav-sections-item-radius",
1117
+ "--nav-sections-item-padding",
1118
+ "--nav-sections-label-color",
1119
+ "--nav-sections-item-color",
1120
+ "--nav-sections-item-background-hover",
1121
+ "--nav-sections-item-background-active",
1122
+ "--nav-sections-accent",
1123
+ "--nav-sections-icon-color",
1124
+ "--nav-sections-group-border",
1125
+ "--nav-sections-footer-border",
1126
+ "--tab-sections-gap",
1127
+ "--tab-sections-tab-padding",
1128
+ "--tab-sections-tab-gap",
1129
+ "--tab-sections-badge-size",
1130
+ "--tab-sections-border",
1131
+ "--tab-sections-color",
1132
+ "--tab-sections-color-active",
1133
+ "--tab-sections-accent",
1134
+ "--tab-sections-badge-background",
1135
+ "--tab-sections-badge-color",
1136
+ "--badge-padding",
1137
+ "--badge-radius",
1138
+ "--badge-font-size",
1139
+ "--badge-font-weight",
1140
+ "--badge-border",
1141
+ "--badge-neutral-background",
1142
+ "--badge-neutral-color",
1143
+ "--badge-accent-background",
1144
+ "--badge-accent-color",
1145
+ "--badge-success-background",
1146
+ "--badge-success-color",
1147
+ "--badge-warning-background",
1148
+ "--badge-warning-color",
1149
+ "--badge-danger-background",
1150
+ "--badge-danger-color",
1151
+ "--brand-font-weight",
1152
+ "--brand-letter-spacing",
1153
+ "--brand-color",
1154
+ "--brand-color-hover",
1155
+ "--empty-state-gap",
1156
+ "--empty-state-padding",
1157
+ "--empty-state-icon-gap",
1158
+ "--empty-state-title-size",
1159
+ "--empty-state-description-size",
1160
+ "--empty-state-max-width",
1161
+ "--empty-state-actions-gap",
1162
+ "--empty-state-actions-margin-top",
1163
+ "--radio-group-gap",
1164
+ "--page-pad",
1165
+ "--page-pad-x",
1166
+ "--page-pad-y",
1167
+ "--page-gap",
1168
+ "--card-radius"
1169
+ ];
1170
+ DEPRECATED_VAR_RE2 = new RegExp(`var\\(\\s*(?:${COMPONENTS_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})(?![\\w-])`, "i");
1171
+ DEPRECATED_DECL_RE2 = new RegExp(`(?:${COMPONENTS_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})(?![\\w-])\\s*:`);
1172
+ });
1173
+
691
1174
  // src/lib/migrate/front.ts
692
1175
  var exports_front = {};
693
1176
  __export(exports_front, {
@@ -714,18 +1197,24 @@ var init_front = __esm(() => {
714
1197
  init_legacy_hacks();
715
1198
  init_form_aliases();
716
1199
  init_form_tokens();
1200
+ init_components();
1201
+ init_css_sanitize();
717
1202
  FRONT_MIGRATIONS = {
718
1203
  "size-sml": migrateSizeSml,
719
1204
  "field-border": migrateFieldBorder,
720
1205
  "modal-create": migrateModalCreate,
721
1206
  "form-aliases": migrateFormAliases,
722
1207
  "form-tokens": migrateFormTokens,
1208
+ components: migrateComponents,
1209
+ "css-sanitize": migrateCssSanitize,
723
1210
  "legacy-hacks": migrateLegacyReactHack,
724
1211
  "socket-server": migrateSocketServer
725
1212
  };
726
1213
  FRONT_MIGRATE_ORDER = [
727
1214
  "form-aliases",
728
1215
  "form-tokens",
1216
+ "components",
1217
+ "css-sanitize",
729
1218
  "size-sml",
730
1219
  "field-border",
731
1220
  "modal-create",
@@ -734,7 +1223,7 @@ var init_front = __esm(() => {
734
1223
  });
735
1224
 
736
1225
  // src/index.ts
737
- import { readFileSync as readFileSync19 } from "fs";
1226
+ import { readFileSync as readFileSync21 } from "fs";
738
1227
  import { dirname as dirname4, join as join18 } from "path";
739
1228
  import { fileURLToPath as fileURLToPath2 } from "url";
740
1229
 
@@ -854,6 +1343,17 @@ function installDeps(cwd = process.cwd()) {
854
1343
  });
855
1344
  return r.status === 0;
856
1345
  }
1346
+ function installGlobalElcrm() {
1347
+ const bun = spawnSync("bun", ["--version"], { encoding: "utf8" });
1348
+ const cmd = bun.status === 0 ? "bun" : "npm";
1349
+ const args = ["i", "-g", "elcrm@latest"];
1350
+ console.log(`CLI: ${cmd} ${args.join(" ")}`);
1351
+ const r = spawnSync(cmd, args, {
1352
+ stdio: "inherit",
1353
+ env: process.env
1354
+ });
1355
+ return r.status === 0;
1356
+ }
857
1357
 
858
1358
  // src/commands/update.ts
859
1359
  init_pkg();
@@ -877,18 +1377,24 @@ var c = {
877
1377
 
878
1378
  // src/commands/update.ts
879
1379
  init_front();
1380
+ init_css_sanitize();
880
1381
 
881
1382
  // src/lib/cssSync.ts
882
1383
  init_form_tokens();
1384
+ init_components();
883
1385
  import {
884
1386
  existsSync as existsSync6,
885
1387
  readdirSync as readdirSync3,
886
- readFileSync as readFileSync10,
1388
+ readFileSync as readFileSync12,
887
1389
  statSync as statSync3,
888
1390
  writeFileSync as writeFileSync4
889
1391
  } from "fs";
890
1392
  import { createRequire } from "module";
891
1393
  import { dirname, join as join6 } from "path";
1394
+ var TOKEN_DEPRECATED = new Set([
1395
+ ...FORM_TOKEN_DEPRECATED,
1396
+ ...COMPONENTS_TOKEN_DEPRECATED
1397
+ ]);
892
1398
  var COLOR_VALUE = /#|rgba?\(|hsla?\(|oklch|oklab|color-mix|lab\(|lch\(|hwb\(/i;
893
1399
  var COLOR_NAME = /(?:^|-)(bg|fg|color|fill|track|thumb|overlay|shadow|border|ring|background|placeholder)(?:-|$)/i;
894
1400
  var GEOM_VALUE = /(?:^|\s)(\d*\.?\d+)(px|em|rem|%|vh|vw|dvh|ch|ex|s|ms|deg|fr|pt)\b|^[0-9.]+$|ease|linear|none/i;
@@ -1008,7 +1514,7 @@ function resolvePkgRoot(pkgName, cwd) {
1008
1514
  }
1009
1515
  function readPkgJson(dir) {
1010
1516
  try {
1011
- return JSON.parse(readFileSync10(join6(dir, "package.json"), "utf8"));
1517
+ return JSON.parse(readFileSync12(join6(dir, "package.json"), "utf8"));
1012
1518
  } catch {
1013
1519
  return null;
1014
1520
  }
@@ -1064,7 +1570,7 @@ function collectPkgTokens(pkgName, cwd) {
1064
1570
  const acc = [];
1065
1571
  const seen = new Set;
1066
1572
  for (const file of files) {
1067
- const css = readFileSync10(file, "utf8");
1573
+ const css = readFileSync12(file, "utf8");
1068
1574
  const kind = fileKind(file);
1069
1575
  if (kind !== "mixed") {
1070
1576
  for (const [name, value] of parseCustomProps(css)) {
@@ -1232,9 +1738,9 @@ function syncElcrmCss(options) {
1232
1738
  const file = fileFor(bucket);
1233
1739
  if (!existsSync6(file))
1234
1740
  continue;
1235
- let css = readFileSync10(file, "utf8");
1741
+ let css = readFileSync12(file, "utf8");
1236
1742
  const have = existingTokenNames(css);
1237
- const missing = allDefs.filter((d) => d.bucket === bucket && !have.has(d.name) && !FORM_TOKEN_DEPRECATED.includes(d.name));
1743
+ const missing = allDefs.filter((d) => d.bucket === bucket && !have.has(d.name) && !TOKEN_DEPRECATED.has(d.name));
1238
1744
  const uniq = [];
1239
1745
  const seen = new Set;
1240
1746
  for (const d of missing) {
@@ -1257,7 +1763,7 @@ function syncElcrmCss(options) {
1257
1763
  writeFileSync4(file, next, "utf8");
1258
1764
  }
1259
1765
  if (existsSync6(paths.elcrm)) {
1260
- let css = readFileSync10(paths.elcrm, "utf8");
1766
+ let css = readFileSync12(paths.elcrm, "utf8");
1261
1767
  for (const exp of cssExports) {
1262
1768
  const specs = [];
1263
1769
  if (exp.light)
@@ -1284,7 +1790,7 @@ function syncElcrmCss(options) {
1284
1790
  import { resolve } from "path";
1285
1791
 
1286
1792
  // src/lib/test/detect.ts
1287
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
1793
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
1288
1794
  import { join as join7 } from "path";
1289
1795
  function hasElcrmServer(pkg) {
1290
1796
  for (const section of [
@@ -1339,17 +1845,43 @@ function readPkgAt(dir) {
1339
1845
  if (!existsSync7(path))
1340
1846
  return null;
1341
1847
  try {
1342
- return JSON.parse(readFileSync11(path, "utf8"));
1848
+ return JSON.parse(readFileSync13(path, "utf8"));
1343
1849
  } catch {
1344
1850
  return null;
1345
1851
  }
1346
1852
  }
1347
1853
 
1348
1854
  // src/lib/test/front.ts
1349
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
1855
+ import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
1350
1856
  import { join as join8, relative as relative2 } from "path";
1351
1857
  init_form_tokens();
1858
+ init_components();
1859
+ init_css_sanitize();
1352
1860
  var SKIP = new Set(["node_modules", "dist", ".git", "coverage", ".bun"]);
1861
+ function isThemeTokenFile(rel2) {
1862
+ const n = rel2.replace(/\\/g, "/");
1863
+ return /(?:^|\/)(?:theme(?:-light|-dark)?|elcrm)\.css$/.test(n);
1864
+ }
1865
+ function isGeneratedIcons(rel2) {
1866
+ return /icons\.generated\.(tsx?|jsx?)$/.test(rel2.replace(/\\/g, "/"));
1867
+ }
1868
+ var HARD_HEX_RE = /#[0-9a-fA-F]{3,8}\b/;
1869
+ var HARD_RGB_RE = /\b(?:rgb|rgba|hsl|hsla)\s*\(/i;
1870
+ var HARD_NAMED_RE = /(?:^|[;{]\s*|(?:color|background(?:-color)?|border-color|fill|stroke|outline-color)\s*:\s*)(?:white|black|red|blue|navy|gray|grey|silver|whitesmoke)\b/i;
1871
+ var JSX_COLOR_PROP_RE = /\b(?:color|background(?:Color)?|borderColor|fill)\s*=\s*\{?\s*["'](?:#|rgb|hsl|white|black)/i;
1872
+ function lineForColorScan(line) {
1873
+ return line.replace(/var\([^)]*\)/g, "").replace(/url\s*\([^)]*\)/gi, "");
1874
+ }
1875
+ function hardcodedColorReason(line) {
1876
+ const s = lineForColorScan(line);
1877
+ if (HARD_HEX_RE.test(s) || HARD_RGB_RE.test(s) || JSX_COLOR_PROP_RE.test(line)) {
1878
+ return "\u043B\u0438\u0442\u0435\u0440\u0430\u043B \u0446\u0432\u0435\u0442\u0430 \u2014 var(--button-*|--field-*|--item-*|--layout-*) \u0438\u0437 theme";
1879
+ }
1880
+ if (HARD_NAMED_RE.test(s) && !/\btransparent\b|\bcurrentColor\b|\binherit\b/.test(s)) {
1881
+ return "\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0446\u0432\u0435\u0442 \u2014 \u0442\u043E\u043A\u0435\u043D @elcrm/* / theme";
1882
+ }
1883
+ return null;
1884
+ }
1353
1885
  var DEPRECATED_SOCKET_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/(?:socket\/server|socket-server)["']/;
1354
1886
  var THEMES_CSS_IMPORT_RE = /(?:from\s+|import\s+)["']@elcrm\/[^"']+\/themes\.css["']|import\s+["']@elcrm\/[^"']+\/themes\.css["']/;
1355
1887
  var LEGACY_PASSKEY_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/passkey["']/;
@@ -1469,8 +2001,9 @@ function collectFrontIssues(cwd, pkg) {
1469
2001
  files.push(inSrc);
1470
2002
  }
1471
2003
  for (const file of files) {
1472
- const text = readFileSync12(file, "utf8");
2004
+ const text = readFileSync14(file, "utf8");
1473
2005
  const rel2 = relative2(cwd, file);
2006
+ const skipHardColor = isLib || isThemeTokenFile(rel2) || isGeneratedIcons(rel2);
1474
2007
  if (DEPRECATED_SOCKET_IMPORT_RE.test(text)) {
1475
2008
  issues.push({
1476
2009
  level: "error",
@@ -1576,12 +2109,66 @@ function collectFrontIssues(cwd, pkg) {
1576
2109
  fixable: true
1577
2110
  });
1578
2111
  }
2112
+ if (lineHasDeprecatedComponentsToken(line)) {
2113
+ issues.push({
2114
+ level: "warn",
2115
+ code: "components-token-legacy",
2116
+ message: "legacy \u0442\u043E\u043A\u0435\u043D @elcrm/components \u2192 --shell-* (elcrm migrate components)",
2117
+ file: rel2,
2118
+ line: i + 1,
2119
+ fixable: true
2120
+ });
2121
+ }
2122
+ if (lineHasOrphanColorMix(line)) {
2123
+ issues.push({
2124
+ level: "error",
2125
+ code: "css-orphan-mix",
2126
+ message: "\u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0439 \u0445\u0432\u043E\u0441\u0442 color-mix (in srgb\u2026) \u2014 elcrm migrate css-sanitize",
2127
+ file: rel2,
2128
+ line: i + 1,
2129
+ fixable: true
2130
+ });
2131
+ }
2132
+ if (/\b(Block|Row|Column)\b/.test(line) && /@elcrm\/components/.test(text) && /from\s+["']@elcrm\/components/.test(text)) {
2133
+ if (/import\s*\{[^}]*\b(Block|Row|Column)\b/.test(line) || /<\/?(?:Block|Row|Column)\b/.test(line)) {
2134
+ issues.push({
2135
+ level: "warn",
2136
+ code: "components-stack",
2137
+ message: "Block/Row/Column \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0438 \u2014 Stack (elcrm migrate components)",
2138
+ file: rel2,
2139
+ line: i + 1,
2140
+ fixable: true
2141
+ });
2142
+ }
2143
+ }
2144
+ if (/\b(activeKey|defaultActiveKey)\s*=/.test(line) && /@elcrm\/components/.test(text)) {
2145
+ issues.push({
2146
+ level: "warn",
2147
+ code: "components-value",
2148
+ message: "activeKey/defaultActiveKey \u2192 value/defaultValue (elcrm migrate components)",
2149
+ file: rel2,
2150
+ line: i + 1,
2151
+ fixable: true
2152
+ });
2153
+ }
2154
+ if (!skipHardColor) {
2155
+ const why = hardcodedColorReason(line);
2156
+ if (why) {
2157
+ issues.push({
2158
+ level: "warn",
2159
+ code: "hardcoded-color",
2160
+ message: why,
2161
+ file: rel2,
2162
+ line: i + 1
2163
+ });
2164
+ }
2165
+ }
1579
2166
  });
1580
2167
  }
1581
2168
  return issues;
1582
2169
  }
1583
2170
  async function runPkgScript(cwd, script) {
1584
- const pkg = JSON.parse(readFileSync12(join8(cwd, "package.json"), "utf8"));
2171
+ const pkg = JSON.parse(readFileSync14(join8(cwd, "package.json"), "utf8"));
1585
2172
  if (!pkg.scripts?.[script])
1586
2173
  return { ran: false, ok: true };
1587
2174
  console.log(`\u2192 bun run ${script}`);
@@ -1624,7 +2211,7 @@ function printIssues(issues, title) {
1624
2211
  }
1625
2212
 
1626
2213
  // src/lib/test/serverScan.ts
1627
- import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync6 } from "fs";
2214
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
1628
2215
  import { join as join9, relative as relative3 } from "path";
1629
2216
 
1630
2217
  // src/lib/test/serverRules.ts
@@ -2026,7 +2613,7 @@ function scanServerProject(root) {
2026
2613
  walkFiles2(root, root, files);
2027
2614
  const findings = [];
2028
2615
  for (const file of files) {
2029
- const text = readFileSync13(file, "utf8");
2616
+ const text = readFileSync15(file, "utf8");
2030
2617
  if (file.endsWith("package.json")) {
2031
2618
  findings.push(...scanPackageJson(root, file, text));
2032
2619
  } else {
@@ -2136,6 +2723,17 @@ elcrm test: ok`));
2136
2723
  // src/commands/update.ts
2137
2724
  async function cmdUpdate(parsed) {
2138
2725
  const cwd = process.cwd();
2726
+ const dry = hasFlag(parsed, "dry");
2727
+ const useCore = hasFlag(parsed, "core");
2728
+ const noInstall = hasFlag(parsed, "no-install");
2729
+ const doFix = hasFlag(parsed, "fix");
2730
+ const doTest = hasFlag(parsed, "test");
2731
+ if (dry) {
2732
+ console.log(`${c.gray("[dry]")} bun/npm i -g elcrm@latest`);
2733
+ } else if (!installGlobalElcrm()) {
2734
+ console.error("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 elcrm.");
2735
+ process.exit(1);
2736
+ }
2139
2737
  const pkg = readPackageJson(cwd);
2140
2738
  if (!pkg) {
2141
2739
  console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u043C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435.");
@@ -2146,11 +2744,6 @@ async function cmdUpdate(parsed) {
2146
2744
  console.log("\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/* \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.");
2147
2745
  return;
2148
2746
  }
2149
- const dry = hasFlag(parsed, "dry");
2150
- const useCore = hasFlag(parsed, "core");
2151
- const noInstall = hasFlag(parsed, "no-install");
2152
- const doFix = hasFlag(parsed, "fix");
2153
- const doTest = hasFlag(parsed, "test");
2154
2747
  const names = uniqueElcrmNames(deps);
2155
2748
  console.log(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E @elcrm/*: ${names.length}`);
2156
2749
  let versions;
@@ -2271,6 +2864,12 @@ package.json \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D (${changed}).`);
2271
2864
  if (added === 0 && css.imports.length === 0) {
2272
2865
  console.log(" \u0422\u043E\u043A\u0435\u043D\u044B \u0443\u0436\u0435 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435.");
2273
2866
  }
2867
+ const cleaned = await migrateCssSanitize(cwd, false);
2868
+ if (cleaned.length) {
2869
+ console.log(" css-sanitize:");
2870
+ for (const ch of cleaned)
2871
+ console.log(` ${ch.file}: ${ch.detail}`);
2872
+ }
2274
2873
  } catch (e) {
2275
2874
  console.log(` ${c.gray("css sync \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D:")} ${e instanceof Error ? e.message : e}`);
2276
2875
  }
@@ -2295,7 +2894,7 @@ import {
2295
2894
  existsSync as existsSync11,
2296
2895
  mkdirSync,
2297
2896
  readdirSync as readdirSync7,
2298
- readFileSync as readFileSync14,
2897
+ readFileSync as readFileSync16,
2299
2898
  statSync as statSync7,
2300
2899
  writeFileSync as writeFileSync5
2301
2900
  } from "fs";
@@ -2339,7 +2938,7 @@ function isEmptyDir(dir) {
2339
2938
  function writePkgName(pkgPath, name) {
2340
2939
  if (!existsSync11(pkgPath))
2341
2940
  return;
2342
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
2941
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
2343
2942
  pkg.name = name;
2344
2943
  pkg.version = pkg.version ?? "0.0.0";
2345
2944
  writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
@@ -2506,7 +3105,7 @@ async function cmdMigrate(parsed) {
2506
3105
  console.log(dry ? `
2507
3106
  --dry: ${changes.length} \u043F\u043E\u0442\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.` : `
2508
3107
  \u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E: ${changes.length}.`);
2509
- if (name === "front" || name === "ui" || name === "size-sml" || name === "form-tokens") {
3108
+ if (name === "front" || name === "ui" || name === "size-sml" || name === "form-tokens" || name === "components" || name === "css-sanitize") {
2510
3109
  console.log("\u0414\u0430\u043B\u044C\u0448\u0435: elcrm css && elcrm audit");
2511
3110
  }
2512
3111
  }
@@ -2701,7 +3300,7 @@ import { resolve as resolve3 } from "path";
2701
3300
  import {
2702
3301
  existsSync as existsSync13,
2703
3302
  readdirSync as readdirSync9,
2704
- readFileSync as readFileSync16,
3303
+ readFileSync as readFileSync18,
2705
3304
  rmSync,
2706
3305
  statSync as statSync8,
2707
3306
  writeFileSync as writeFileSync7
@@ -2709,12 +3308,24 @@ import {
2709
3308
  import { join as join13 } from "path";
2710
3309
 
2711
3310
  // src/vite/concat-css.ts
2712
- import { readdirSync as readdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
3311
+ import { readdirSync as readdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
2713
3312
  import { join as join12 } from "path";
3313
+ var SKIP_CSS = new Set([
3314
+ "index.css",
3315
+ "tokens.css",
3316
+ "light.css",
3317
+ "dark.css",
3318
+ "themes.css"
3319
+ ]);
2714
3320
  function concatDistCss(options) {
2715
3321
  const outFile = options.outFile ?? "index.css";
2716
- const files = readdirSync8(options.distDir).filter((f) => f.endsWith(".css") && f !== outFile).sort();
2717
- const css = files.map((f) => readFileSync15(join12(options.distDir, f), "utf8")).join(`
3322
+ const skip = new Set([
3323
+ ...SKIP_CSS,
3324
+ outFile,
3325
+ ...options.skip ?? []
3326
+ ]);
3327
+ const files = readdirSync8(options.distDir).filter((f) => f.endsWith(".css") && !skip.has(f)).sort();
3328
+ const css = files.map((f) => readFileSync17(join12(options.distDir, f), "utf8")).join(`
2718
3329
  `);
2719
3330
  const outPath = join12(options.distDir, outFile);
2720
3331
  writeFileSync6(outPath, css);
@@ -2745,7 +3356,7 @@ function postbuildLib(options) {
2745
3356
  const flat = join13(dist, `${name}.d.ts`);
2746
3357
  const src = existsSync13(impl) ? impl : existsSync13(nestedIndex) ? nestedIndex : null;
2747
3358
  if (src) {
2748
- const text = readFileSync16(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
3359
+ const text = readFileSync18(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
2749
3360
  writeFileSync7(flat, text);
2750
3361
  console.log(`[postbuild] types \u2192 ${name}.d.ts`);
2751
3362
  }
@@ -2764,6 +3375,7 @@ async function cmdPostbuild(parsed) {
2764
3375
 
2765
3376
  // src/commands/css.ts
2766
3377
  init_pkg();
3378
+ init_css_sanitize();
2767
3379
  async function cmdCss(parsed) {
2768
3380
  const cwd = parsed.options.dir || process.cwd();
2769
3381
  const pkg = readPackageJson(cwd);
@@ -2805,6 +3417,14 @@ async function cmdCss(parsed) {
2805
3417
  } else if (dry) {
2806
3418
  console.log("\u0417\u0430\u043F\u0438\u0441\u044C \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u043B\u0430\u0441\u044C (--dry).");
2807
3419
  }
3420
+ const cleaned = await migrateCssSanitize(cwd, dry);
3421
+ if (cleaned.length) {
3422
+ console.log(dry ? `
3423
+ [dry] css-sanitize: ${cleaned.length} \u0444\u0430\u0439\u043B(\u043E\u0432)` : `
3424
+ css-sanitize: \u043F\u043E\u0447\u0438\u043D\u0435\u043D\u043E ${cleaned.length} \u0444\u0430\u0439\u043B(\u043E\u0432)`);
3425
+ for (const ch of cleaned)
3426
+ console.log(` ${ch.file}: ${ch.detail}`);
3427
+ }
2808
3428
  } catch (e) {
2809
3429
  console.error(e instanceof Error ? e.message : e);
2810
3430
  process.exit(1);
@@ -2812,12 +3432,14 @@ async function cmdCss(parsed) {
2812
3432
  }
2813
3433
 
2814
3434
  // src/commands/audit.ts
2815
- import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
3435
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
2816
3436
  import { join as join14, relative as relative4 } from "path";
2817
3437
  init_pkg();
2818
3438
  init_walk();
2819
3439
  init_front();
2820
3440
  init_form_tokens();
3441
+ init_components();
3442
+ init_css_sanitize();
2821
3443
  function scan(cwd) {
2822
3444
  const out = [];
2823
3445
  const pkg = readPackageJson(cwd);
@@ -2848,7 +3470,7 @@ function scan(cwd) {
2848
3470
  }
2849
3471
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
2850
3472
  for (const file of files) {
2851
- const text = readFileSync17(file, "utf8");
3473
+ const text = readFileSync19(file, "utf8");
2852
3474
  const rel2 = relative4(cwd, file);
2853
3475
  const lines = text.split(/\r?\n/);
2854
3476
  lines.forEach((line, i) => {
@@ -2930,7 +3552,7 @@ function scan(cwd) {
2930
3552
  return false;
2931
3553
  if (/theme(-light|-dark)?\.css$/.test(f))
2932
3554
  return false;
2933
- const t = readFileSync17(f, "utf8");
3555
+ const t = readFileSync19(f, "utf8");
2934
3556
  return t.includes("--search-border:") && t.includes("--button-secondary-bg:") && t.includes("transparent");
2935
3557
  });
2936
3558
  if (toolbarMarkers.length > 1) {
@@ -2957,7 +3579,7 @@ function scan(cwd) {
2957
3579
  "theme-dark.css",
2958
3580
  "theme.css"
2959
3581
  ].map((f) => join14(styleDir, f));
2960
- const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync17(f, "utf8")).join(`
3582
+ const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync19(f, "utf8")).join(`
2961
3583
  `);
2962
3584
  if (themeText && !/--popup-shadow\s*:/.test(themeText) && /--date-popup-shadow\s*:|--select-background\s*:/.test(themeText)) {
2963
3585
  out.push({
@@ -2979,7 +3601,7 @@ function scan(cwd) {
2979
3601
  for (const file of files) {
2980
3602
  if (!file.endsWith(".css"))
2981
3603
  continue;
2982
- const text = readFileSync17(file, "utf8");
3604
+ const text = readFileSync19(file, "utf8");
2983
3605
  const relPath = relative4(cwd, file);
2984
3606
  const lines = text.split(/\r?\n/);
2985
3607
  lines.forEach((line, i) => {
@@ -2995,6 +3617,26 @@ function scan(cwd) {
2995
3617
  fix: "elcrm migrate form-tokens"
2996
3618
  });
2997
3619
  }
3620
+ if (lineHasDeprecatedComponentsToken(line)) {
3621
+ out.push({
3622
+ level: "warn",
3623
+ code: "components-token-legacy",
3624
+ message: "legacy components-\u0442\u043E\u043A\u0435\u043D \u2192 --shell-*",
3625
+ file: relPath,
3626
+ line: i + 1,
3627
+ fix: "elcrm migrate components"
3628
+ });
3629
+ }
3630
+ if (lineHasOrphanColorMix(line)) {
3631
+ out.push({
3632
+ level: "error",
3633
+ code: "css-orphan-mix",
3634
+ message: "\u0431\u0438\u0442\u044B\u0439 CSS: \u0445\u0432\u043E\u0441\u0442 color-mix \u0431\u0435\u0437 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 (in srgb / transparent))",
3635
+ file: relPath,
3636
+ line: i + 1,
3637
+ fix: "elcrm migrate css-sanitize"
3638
+ });
3639
+ }
2998
3640
  });
2999
3641
  }
3000
3642
  return out;
@@ -3108,7 +3750,7 @@ import {
3108
3750
  existsSync as existsSync15,
3109
3751
  mkdirSync as mkdirSync2,
3110
3752
  readdirSync as readdirSync10,
3111
- readFileSync as readFileSync18,
3753
+ readFileSync as readFileSync20,
3112
3754
  writeFileSync as writeFileSync8
3113
3755
  } from "fs";
3114
3756
  import { dirname as dirname3, join as join15 } from "path";
@@ -3126,7 +3768,7 @@ function readBundled(relPath) {
3126
3768
  if (!existsSync15(full)) {
3127
3769
  throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${relPath}`);
3128
3770
  }
3129
- return readFileSync18(full, "utf8");
3771
+ return readFileSync20(full, "utf8");
3130
3772
  }
3131
3773
 
3132
3774
  // src/commands/docs.ts
@@ -3153,8 +3795,13 @@ async function cmdDocs(parsed) {
3153
3795
 
3154
3796
  // src/commands/cursor.ts
3155
3797
  import { join as join17, resolve as resolve5 } from "path";
3156
- var ALWAYS = ["elcrm.mdc", "elcrm-packages.mdc"];
3157
- var UI = ["elcrm-ui.mdc", "elcrm-module.mdc", "elcrm-theme.mdc"];
3798
+ var ALWAYS = ["elcrm.mdc", "elcrm-packages.mdc", "elcrm-jsdoc.mdc"];
3799
+ var UI = [
3800
+ "elcrm-ui.mdc",
3801
+ "elcrm-module.mdc",
3802
+ "elcrm-theme.mdc",
3803
+ "elcrm-tokens.mdc"
3804
+ ];
3158
3805
  var SERVER = ["elcrm-server.mdc", "elcrm-rpc.mdc"];
3159
3806
  var LIB = ["elcrm-lib.mdc"];
3160
3807
  function writeOne(dest, body, dry, force, label) {
@@ -3199,7 +3846,7 @@ function cliVersion() {
3199
3846
  join18(here, "package.json")
3200
3847
  ]) {
3201
3848
  try {
3202
- const v = JSON.parse(readFileSync19(p, "utf8")).version;
3849
+ const v = JSON.parse(readFileSync21(p, "utf8")).version;
3203
3850
  if (typeof v === "string" && v)
3204
3851
  return v;
3205
3852
  } catch {}
@@ -3217,7 +3864,8 @@ function showHelp() {
3217
3864
 
3218
3865
  \u041A\u043E\u043C\u0430\u043D\u0434\u044B:
3219
3866
  update [--dry] [--core] [--no-install] [--fix] [--test]
3220
- \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 @elcrm/* \u0434\u043E latest; --fix = migrate front; --test = elcrm test
3867
+ CLI (bun i -g elcrm@latest) + \u0432\u0441\u0435 @elcrm/* \u0432 \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u0434\u043E latest
3868
+ --fix = migrate front; --test = elcrm test
3221
3869
  \u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm update --fix --test
3222
3870
 
3223
3871
  create <name> [--template app|lib|panel] [--no-install]
@@ -3229,7 +3877,7 @@ function showHelp() {
3229
3877
 
3230
3878
  migrate <name> [--dry]
3231
3879
  migrate --list
3232
- \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438: front|ui, form-aliases, form-tokens, size-sml, field-border, modal-create, legacy-hacks, socket-server
3880
+ \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438: front|ui, form-aliases, form-tokens, components, css-sanitize, size-sml, field-border, modal-create, legacy-hacks, socket-server
3233
3881
 
3234
3882
  audit [--fix] [--dry] [--dir=\u2026]
3235
3883
  \u0423\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 API + CSS-\u0442\u043E\u043A\u0435\u043D\u044B form (padding-\u0430\u043B\u0438\u0430\u0441\u044B, popup-shadow, border shorthand)