elcrm 1.1.18 → 1.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // @bun
3
3
 
4
4
  // src/index.ts
5
- import { readFileSync as readFileSync23 } from "fs";
6
- import { dirname as dirname4, join as join18 } from "path";
5
+ import { readFileSync as readFileSync27 } from "fs";
6
+ import { dirname as dirname4, join as join23 } from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
 
9
9
  // src/parseArgs.ts
@@ -35,7 +35,9 @@ function parseArgs(argv = process.argv.slice(2)) {
35
35
  "name",
36
36
  "dir",
37
37
  "folder",
38
- "out"
38
+ "out",
39
+ "api",
40
+ "web"
39
41
  ]);
40
42
  if (valueOpts.has(body)) {
41
43
  options[body] = next;
@@ -3669,10 +3671,11 @@ async function cmdMigrate(parsed) {
3669
3671
  }
3670
3672
 
3671
3673
  // src/commands/doctor.ts
3672
- import { resolve as resolve5 } from "path";
3674
+ import { resolve as resolve9 } from "path";
3673
3675
 
3674
- // src/commands/docs.ts
3675
- import { join as join13, resolve as resolve3 } from "path";
3676
+ // src/lib/kit/doctor-kit.ts
3677
+ import { existsSync as existsSync14 } from "fs";
3678
+ import { join as join16, resolve as resolve6 } from "path";
3676
3679
 
3677
3680
  // src/lib/projectKind.ts
3678
3681
  function detectProjectKind(cwd) {
@@ -3702,6 +3705,370 @@ function isElcrmLibPackage(cwd) {
3702
3705
  return typeof name === "string" && name.startsWith("@elcrm/");
3703
3706
  }
3704
3707
 
3708
+ // src/lib/kit/paths.ts
3709
+ import { existsSync as existsSync12, readFileSync as readFileSync19 } from "fs";
3710
+ import { join as join12, resolve as resolve3 } from "path";
3711
+ function readName(dir) {
3712
+ try {
3713
+ const j = JSON.parse(readFileSync19(join12(dir, "package.json"), "utf8"));
3714
+ return j.name ?? null;
3715
+ } catch {
3716
+ return null;
3717
+ }
3718
+ }
3719
+ function findEluiApiDir(from) {
3720
+ const env = process.env.ELUI_API_DIR?.trim();
3721
+ if (env && existsSync12(join12(env, "src/kit/catalog.ts")))
3722
+ return resolve3(env);
3723
+ let dir = resolve3(from);
3724
+ for (let i = 0;i < 8; i++) {
3725
+ for (const name of ["elUI.api", "ui-kit.api"]) {
3726
+ const candidate = join12(dir, name);
3727
+ if (existsSync12(join12(candidate, "src/kit/catalog.ts")))
3728
+ return candidate;
3729
+ }
3730
+ const parent = join12(dir, "..");
3731
+ if (parent === dir)
3732
+ break;
3733
+ dir = parent;
3734
+ }
3735
+ return null;
3736
+ }
3737
+ function findEluiWebDir(from) {
3738
+ const env = process.env.ELUI_WEB_DIR?.trim();
3739
+ if (env && readName(env) === "elui-web")
3740
+ return resolve3(env);
3741
+ let dir = resolve3(from);
3742
+ for (let i = 0;i < 8; i++) {
3743
+ for (const name of ["elUI.web", "Uikit.web"]) {
3744
+ const candidate = join12(dir, name);
3745
+ if (readName(candidate) === "elui-web")
3746
+ return candidate;
3747
+ }
3748
+ dir = join12(dir, "..");
3749
+ }
3750
+ return null;
3751
+ }
3752
+ function kitPkgSlug(npmName) {
3753
+ if (npmName === "@elcrm/components")
3754
+ return "components";
3755
+ if (npmName === "@elcrm/form")
3756
+ return "form";
3757
+ return null;
3758
+ }
3759
+ function manifestFileName(npmName) {
3760
+ return npmName.replace("@", "").replace("/", "-") + ".json";
3761
+ }
3762
+
3763
+ // src/lib/kit/sync.ts
3764
+ import { existsSync as existsSync13, mkdirSync as mkdirSync3, readFileSync as readFileSync22, writeFileSync as writeFileSync7 } from "fs";
3765
+ import { join as join15, resolve as resolve5 } from "path";
3766
+ import { spawnSync as spawnSync2 } from "child_process";
3767
+
3768
+ // src/lib/kit/emit.ts
3769
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync21, writeFileSync as writeFileSync6 } from "fs";
3770
+ import { join as join14, resolve as resolve4 } from "path";
3771
+
3772
+ // src/lib/kit/parse-dts.ts
3773
+ import { readFileSync as readFileSync20, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
3774
+ import { basename, join as join13 } from "path";
3775
+ var SKIP_SLUGS = new Set([
3776
+ "index",
3777
+ "types",
3778
+ "cx",
3779
+ "overlayZ",
3780
+ "lazyRoute",
3781
+ "stackGap",
3782
+ "rovingKeys",
3783
+ "CheckIcon",
3784
+ "useControllableValue",
3785
+ "badgeText"
3786
+ ]);
3787
+ function isMainComponentDoc(doc) {
3788
+ if (!doc)
3789
+ return false;
3790
+ if (doc.includes("@example"))
3791
+ return true;
3792
+ const lines = doc.split(`
3793
+ `).filter((l) => l.trim());
3794
+ if (lines.length >= 2)
3795
+ return true;
3796
+ if (/^Props\s/i.test(doc.trim()))
3797
+ return false;
3798
+ return doc.length > 72;
3799
+ }
3800
+ function componentDoc(content, slug) {
3801
+ const fnAnchor = "export declare function " + slug;
3802
+ const propsAnchor = "export type " + slug + "Props";
3803
+ const fnDoc = leadingDoc(content, fnAnchor);
3804
+ const propsDoc = leadingDoc(content, propsAnchor);
3805
+ if (isMainComponentDoc(fnDoc))
3806
+ return fnDoc;
3807
+ if (isMainComponentDoc(propsDoc))
3808
+ return propsDoc;
3809
+ return fnDoc || propsDoc;
3810
+ }
3811
+ function leadingDoc(content, anchor) {
3812
+ const idx = content.indexOf(anchor);
3813
+ if (idx < 0)
3814
+ return "";
3815
+ const head = content.slice(0, idx).trimEnd();
3816
+ const start = head.lastIndexOf("/**");
3817
+ if (start < 0)
3818
+ return "";
3819
+ const end = head.indexOf("*/", start);
3820
+ if (end < 0)
3821
+ return "";
3822
+ return head.slice(start + 3, end).split(`
3823
+ `).map((l) => l.replace(/^\s*\*\s?/, "").trimEnd()).join(`
3824
+ `).trim();
3825
+ }
3826
+ function examplesFromDoc(doc) {
3827
+ const out = [];
3828
+ const fence = String.fromCharCode(96, 96, 96);
3829
+ const re = new RegExp("@example\\s*\\n?\\s*" + fence + "[\\w]*\\s*\\n([\\s\\S]*?)" + fence, "g");
3830
+ let m;
3831
+ let i = 0;
3832
+ while (m = re.exec(doc)) {
3833
+ const code = m[1].trim();
3834
+ if (!code)
3835
+ continue;
3836
+ out.push({
3837
+ title: i === 0 ? "\u041F\u0440\u0438\u043C\u0435\u0440" : "\u041F\u0440\u0438\u043C\u0435\u0440 " + (i + 1),
3838
+ code
3839
+ });
3840
+ i++;
3841
+ }
3842
+ return out;
3843
+ }
3844
+ function descriptionFromDoc(doc) {
3845
+ const lines = doc.split(`
3846
+ `).map((l) => l.trim()).filter(Boolean);
3847
+ const parts = [];
3848
+ for (const line of lines) {
3849
+ if (line.startsWith("@"))
3850
+ break;
3851
+ parts.push(line);
3852
+ }
3853
+ return parts.join(" ").replace(/\s+/g, " ").trim();
3854
+ }
3855
+ function propsFromPropsBlock(block) {
3856
+ const out = [];
3857
+ const re = /\/\*\*([\s\S]*?)\*\/\s*(?:readonly\s+)?(\w+)\??\s*:\s*([^;]+);/g;
3858
+ let m;
3859
+ while (m = re.exec(block)) {
3860
+ const name = m[2];
3861
+ if (name === "className" || name.startsWith("__"))
3862
+ continue;
3863
+ const doc = m[1].split(`
3864
+ `).map((l) => l.replace(/^\s*\*\s?/, "").trim()).filter(Boolean).join(" ");
3865
+ out.push({
3866
+ name,
3867
+ type: m[3].replace(/\s+/g, " ").trim(),
3868
+ description: doc
3869
+ });
3870
+ }
3871
+ return out;
3872
+ }
3873
+ function parseComponentDts(content, slug) {
3874
+ if (!new RegExp("export declare function " + slug + "\\b").test(content)) {
3875
+ return null;
3876
+ }
3877
+ const doc = componentDoc(content, slug);
3878
+ const propsMatch = content.match(new RegExp("export type " + slug + "Props\\s*=\\s*([\\s\\S]*?);\\s*(?:export|$)"));
3879
+ const propsBlock = propsMatch?.[1] ?? "";
3880
+ return {
3881
+ slug,
3882
+ title: slug,
3883
+ description: descriptionFromDoc(doc) || slug,
3884
+ importFrom: "@elcrm/components",
3885
+ examples: examplesFromDoc(doc),
3886
+ props: propsFromPropsBlock(propsBlock)
3887
+ };
3888
+ }
3889
+ function scanDistComponents(distDir, importFrom) {
3890
+ const out = [];
3891
+ for (const name of readdirSync8(distDir)) {
3892
+ if (!name.endsWith(".d.ts"))
3893
+ continue;
3894
+ const slug = basename(name, ".d.ts");
3895
+ if (SKIP_SLUGS.has(slug) || slug.includes("."))
3896
+ continue;
3897
+ const path = join13(distDir, name);
3898
+ if (!statSync8(path).isFile())
3899
+ continue;
3900
+ const content = readFileSync20(path, "utf8");
3901
+ const parsed = parseComponentDts(content, slug);
3902
+ if (!parsed)
3903
+ continue;
3904
+ parsed.importFrom = importFrom;
3905
+ out.push(parsed);
3906
+ }
3907
+ out.sort((a, b) => a.slug.localeCompare(b.slug));
3908
+ return out;
3909
+ }
3910
+
3911
+ // src/lib/kit/emit.ts
3912
+ function readPkg(cwd) {
3913
+ const j = JSON.parse(readFileSync21(join14(cwd, "package.json"), "utf8"));
3914
+ if (!j.name?.startsWith("@elcrm/")) {
3915
+ throw new Error("elcrm kit emit \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 npm-\u043F\u0430\u043A\u0435\u0442\u0435 @elcrm/*");
3916
+ }
3917
+ return { name: j.name, version: j.version ?? "0.0.0" };
3918
+ }
3919
+ function emitKitManifest(cwd) {
3920
+ const pkg = readPkg(cwd);
3921
+ const pkgSlug = kitPkgSlug(pkg.name);
3922
+ if (!pkgSlug) {
3923
+ throw new Error(`elcrm kit emit \u2014 \u043F\u0430\u043A\u0435\u0442 ${pkg.name} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F (\u043D\u0443\u0436\u0435\u043D components|form)`);
3924
+ }
3925
+ const distDir = join14(cwd, "dist");
3926
+ const components = scanDistComponents(distDir, pkg.name).map((c2) => ({
3927
+ ...c2,
3928
+ importFrom: pkg.name
3929
+ }));
3930
+ return {
3931
+ format: "elcrm-kit-manifest-v1",
3932
+ package: pkg.name,
3933
+ version: pkg.version,
3934
+ pkg: pkgSlug,
3935
+ generatedAt: new Date().toISOString(),
3936
+ components
3937
+ };
3938
+ }
3939
+ function writeKitManifest(cwd, manifest, dry = false) {
3940
+ const written = [];
3941
+ const targets = [
3942
+ join14(cwd, ".elcrm", "kit-manifest.json"),
3943
+ join14(cwd, "dist", "kit-manifest.json")
3944
+ ];
3945
+ const body = `${JSON.stringify(manifest, null, 2)}
3946
+ `;
3947
+ for (const path of targets) {
3948
+ if (dry) {
3949
+ written.push(path);
3950
+ continue;
3951
+ }
3952
+ mkdirSync2(join14(path, ".."), { recursive: true });
3953
+ writeFileSync6(path, body, "utf8");
3954
+ written.push(path);
3955
+ }
3956
+ return written;
3957
+ }
3958
+ function readKitManifest(path) {
3959
+ return JSON.parse(readFileSync21(resolve4(path), "utf8"));
3960
+ }
3961
+
3962
+ // src/lib/kit/sync.ts
3963
+ function patchPkgVersions(apiDir, npmName, version, dry) {
3964
+ const path = join15(apiDir, "src/kit/pkg-versions.ts");
3965
+ let src = readFileSync22(path, "utf8");
3966
+ const re = new RegExp(`("${npmName.replace("/", "\\/")}"\\s*:\\s*")[^"]+(")`);
3967
+ if (!re.test(src)) {
3968
+ console.warn(c.yellow(` ! FALLBACK_DEPS: \u043D\u0435\u0442 ${npmName} \u0432 pkg-versions.ts`));
3969
+ return;
3970
+ }
3971
+ const next = src.replace(re, `$1${version}$2`);
3972
+ if (next === src)
3973
+ return;
3974
+ if (!dry)
3975
+ writeFileSync7(path, next, "utf8");
3976
+ console.log(` ${dry ? "~" : c.green("+")} pkg-versions \u2192 ${npmName} ${version}`);
3977
+ }
3978
+ function patchWebDep(webDir, npmName, version, dry) {
3979
+ const path = join15(webDir, "package.json");
3980
+ const j = JSON.parse(readFileSync22(path, "utf8"));
3981
+ if (!j.dependencies?.[npmName])
3982
+ return;
3983
+ j.dependencies[npmName] = version;
3984
+ if (!dry)
3985
+ writeFileSync7(path, `${JSON.stringify(j, null, 2)}
3986
+ `, "utf8");
3987
+ console.log(` ${dry ? "~" : c.green("+")} elUI.web \u2192 ${npmName}@${version}`);
3988
+ }
3989
+ function runReseed(apiDir, webDir, dry) {
3990
+ if (dry) {
3991
+ console.log(c.gray(" [dry] bun run kit:reseed"));
3992
+ return;
3993
+ }
3994
+ const env = { ...process.env, KIT_RESEED: "1" };
3995
+ if (webDir)
3996
+ env.ELUI_WEB_DIR = webDir;
3997
+ const r = spawnSync2("bun", ["run", "kit:reseed"], {
3998
+ cwd: apiDir,
3999
+ env,
4000
+ stdio: "inherit"
4001
+ });
4002
+ if (r.status !== 0)
4003
+ throw new Error("kit:reseed \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439");
4004
+ }
4005
+ async function syncKitManifest(opts) {
4006
+ const cwd = resolve5(opts.cwd);
4007
+ const dry = opts.dry ?? false;
4008
+ const reseed = opts.reseed !== false;
4009
+ let manifest;
4010
+ if (opts.manifestPath) {
4011
+ manifest = readKitManifest(opts.manifestPath);
4012
+ } else if (existsSync13(join15(cwd, ".elcrm", "kit-manifest.json"))) {
4013
+ manifest = readKitManifest(join15(cwd, ".elcrm", "kit-manifest.json"));
4014
+ } else {
4015
+ manifest = emitKitManifest(cwd);
4016
+ }
4017
+ const apiDir = opts.apiDir ? resolve5(opts.apiDir) : findEluiApiDir(cwd);
4018
+ if (!apiDir) {
4019
+ throw new Error("\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D elUI.api (\u0437\u0430\u0434\u0430\u0439\u0442\u0435 --api=\u2026 \u0438\u043B\u0438 ELUI_API_DIR). Manifest \u043C\u043E\u0436\u043D\u043E \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C: elcrm kit emit");
4020
+ }
4021
+ const outDir = join15(apiDir, "src/kit/manifests");
4022
+ const outFile = join15(outDir, manifestFileName(manifest.package));
4023
+ if (!dry) {
4024
+ mkdirSync3(outDir, { recursive: true });
4025
+ writeFileSync7(outFile, `${JSON.stringify(manifest, null, 2)}
4026
+ `, "utf8");
4027
+ }
4028
+ console.log(`${dry ? "[dry] " : ""}sync \u2192 ${outFile} (${manifest.components.length} \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432, ${manifest.version})`);
4029
+ patchPkgVersions(apiDir, manifest.package, manifest.version, dry);
4030
+ const webDir = opts.webDir ? resolve5(opts.webDir) : opts.patchWeb !== false ? findEluiWebDir(cwd) : null;
4031
+ if (webDir)
4032
+ patchWebDep(webDir, manifest.package, manifest.version, dry);
4033
+ if (reseed)
4034
+ runReseed(apiDir, webDir, dry);
4035
+ }
4036
+ async function emitAndSync(opts) {
4037
+ const manifest = emitKitManifest(opts.cwd);
4038
+ writeKitManifest(opts.cwd, manifest, opts.dry);
4039
+ console.log(`elcrm kit emit: ${manifest.components.length} \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432 \u2192 .elcrm/ + dist/`);
4040
+ await syncKitManifest({ ...opts, cwd: opts.cwd, manifestPath: undefined });
4041
+ }
4042
+
4043
+ // src/lib/kit/doctor-kit.ts
4044
+ async function doctorKitSync(opts) {
4045
+ const cwd = resolve6(opts.cwd);
4046
+ const dry = opts.dry ?? false;
4047
+ if (!isElcrmLibPackage(cwd))
4048
+ return false;
4049
+ const pkg = readPkgAt(cwd);
4050
+ if (!pkg?.name || !kitPkgSlug(pkg.name))
4051
+ return false;
4052
+ if (!existsSync14(join16(cwd, "dist"))) {
4053
+ console.log(c.dim(`
4054
+ kit: \u043F\u0440\u043E\u043F\u0443\u0441\u043A \u2014 \u043D\u0435\u0442 dist/ (\u0441\u043D\u0430\u0447\u0430\u043B\u0430 npm run build)`));
4055
+ return false;
4056
+ }
4057
+ console.log(`
4058
+ ${dry ? "[dry] " : ""}kit: sync \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438 elUI (${pkg.name})\u2026`);
4059
+ try {
4060
+ await emitAndSync({ cwd, dry, reseed: false });
4061
+ return true;
4062
+ } catch (err) {
4063
+ console.warn(c.yellow("kit: " + (err instanceof Error ? err.message : String(err))));
4064
+ console.warn(c.dim(" \u0417\u0430\u0434\u0430\u0439\u0442\u0435 ELUI_API_DIR=\u2026 \u0438\u043B\u0438 elcrm kit pull --api=\u2026/elUI.api"));
4065
+ return false;
4066
+ }
4067
+ }
4068
+
4069
+ // src/commands/docs.ts
4070
+ import { join as join18, resolve as resolve7 } from "path";
4071
+
3705
4072
  // src/lib/elcrmDocsCatalog.ts
3706
4073
  var ALL_ELCRM_DOCS = [
3707
4074
  "CLI.elCRM.md",
@@ -3734,33 +4101,33 @@ function docsForProject(kind, lib) {
3734
4101
 
3735
4102
  // src/lib/scaffoldFiles.ts
3736
4103
  import {
3737
- existsSync as existsSync12,
3738
- mkdirSync as mkdirSync2,
3739
- readdirSync as readdirSync8,
3740
- readFileSync as readFileSync19,
3741
- writeFileSync as writeFileSync6
4104
+ existsSync as existsSync15,
4105
+ mkdirSync as mkdirSync4,
4106
+ readdirSync as readdirSync9,
4107
+ readFileSync as readFileSync23,
4108
+ writeFileSync as writeFileSync8
3742
4109
  } from "fs";
3743
- import { dirname as dirname3, join as join12 } from "path";
4110
+ import { dirname as dirname3, join as join17 } from "path";
3744
4111
  function writeScaffoldFile(dest, body, opts) {
3745
4112
  if (opts.dry)
3746
4113
  return "dry";
3747
- if (!opts.force && existsSync12(dest))
4114
+ if (!opts.force && existsSync15(dest))
3748
4115
  return "skip";
3749
- mkdirSync2(dirname3(dest), { recursive: true });
3750
- writeFileSync6(dest, body, "utf8");
4116
+ mkdirSync4(dirname3(dest), { recursive: true });
4117
+ writeFileSync8(dest, body, "utf8");
3751
4118
  return "write";
3752
4119
  }
3753
4120
  function readBundled(relPath) {
3754
- const full = join12(packageRoot(), "templates", relPath);
3755
- if (!existsSync12(full)) {
4121
+ const full = join17(packageRoot(), "templates", relPath);
4122
+ if (!existsSync15(full)) {
3756
4123
  throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${relPath}`);
3757
4124
  }
3758
- return readFileSync19(full, "utf8");
4125
+ return readFileSync23(full, "utf8");
3759
4126
  }
3760
4127
 
3761
4128
  // src/commands/docs.ts
3762
4129
  async function cmdDocs(parsed) {
3763
- const cwd = resolve3(parsed.options.dir || process.cwd());
4130
+ const cwd = resolve7(parsed.options.dir || process.cwd());
3764
4131
  const dry = hasFlag(parsed, "dry");
3765
4132
  const force = hasFlag(parsed, "force") || !hasFlag(parsed, "keep");
3766
4133
  const lib = hasFlag(parsed, "lib") || isElcrmLibPackage(cwd);
@@ -3768,8 +4135,8 @@ async function cmdDocs(parsed) {
3768
4135
  const names = docsForProject(kind, lib);
3769
4136
  console.log(`${dry ? "[dry] " : ""}elcrm docs \u2192 ${cwd}/docs (${kind}${lib ? ", lib" : ""})`);
3770
4137
  for (const name of names) {
3771
- const body = readBundled(join13("elcrm-docs", name));
3772
- const dest = join13(cwd, "docs", name);
4138
+ const body = readBundled(join18("elcrm-docs", name));
4139
+ const dest = join18(cwd, "docs", name);
3773
4140
  const action = writeScaffoldFile(dest, body, { dry, force });
3774
4141
  const mark = action === "write" ? c.green("+") : action === "skip" ? c.gray("=") : c.cyan("~");
3775
4142
  console.log(` ${mark} docs/${name}`);
@@ -3781,7 +4148,7 @@ async function cmdDocs(parsed) {
3781
4148
  }
3782
4149
 
3783
4150
  // src/commands/cursor.ts
3784
- import { join as join14, resolve as resolve4 } from "path";
4151
+ import { join as join19, resolve as resolve8 } from "path";
3785
4152
  var ALWAYS = ["elcrm.mdc", "elcrm-packages.mdc", "elcrm-jsdoc.mdc"];
3786
4153
  var UI = [
3787
4154
  "elcrm-ui.mdc",
@@ -3797,7 +4164,7 @@ function writeOne(dest, body, dry, force, label) {
3797
4164
  console.log(` ${mark} ${label}`);
3798
4165
  }
3799
4166
  async function cmdCursor(parsed) {
3800
- const cwd = resolve4(parsed.options.dir || process.cwd());
4167
+ const cwd = resolve8(parsed.options.dir || process.cwd());
3801
4168
  const dry = hasFlag(parsed, "dry");
3802
4169
  const force = hasFlag(parsed, "force") || !hasFlag(parsed, "keep");
3803
4170
  const lib = hasFlag(parsed, "lib") || isElcrmLibPackage(cwd);
@@ -3810,24 +4177,24 @@ async function cmdCursor(parsed) {
3810
4177
  ];
3811
4178
  console.log(`${dry ? "[dry] " : ""}elcrm cursor \u2192 ${cwd}/.cursor (${kind}${lib ? ", lib" : ""})`);
3812
4179
  for (const name of ruleNames) {
3813
- let body = readBundled(join14("elcrm-cursor", "rules", name));
4180
+ let body = readBundled(join19("elcrm-cursor", "rules", name));
3814
4181
  if (kind === "server" && (name === "elcrm-server.mdc" || name === "elcrm-rpc.mdc")) {
3815
4182
  body = body.replace("alwaysApply: false", "alwaysApply: true").replace(/^globs:.*\n/m, "");
3816
4183
  }
3817
- writeOne(join14(cwd, ".cursor", "rules", name), body, dry, force, `.cursor/rules/${name}`);
4184
+ writeOne(join19(cwd, ".cursor", "rules", name), body, dry, force, `.cursor/rules/${name}`);
3818
4185
  }
3819
4186
  const docs = docsForProject(kind, lib);
3820
4187
  for (const name of docs) {
3821
- const body = readBundled(join14("elcrm-docs", name));
3822
- writeOne(join14(cwd, ".cursor", "docs", name), body, dry, force, `.cursor/docs/${name}`);
4188
+ const body = readBundled(join19("elcrm-docs", name));
4189
+ writeOne(join19(cwd, ".cursor", "docs", name), body, dry, force, `.cursor/docs/${name}`);
3823
4190
  }
3824
- writeOne(join14(cwd, "AGENTS.md"), readBundled(join14("elcrm-cursor", "AGENTS.md")), dry, force, "AGENTS.md");
4191
+ writeOne(join19(cwd, "AGENTS.md"), readBundled(join19("elcrm-cursor", "AGENTS.md")), dry, force, "AGENTS.md");
3825
4192
  console.log(c.gray("\u0410\u0433\u0435\u043D\u0442: AGENTS.md + rules (\u0443\u0437\u043A\u0438\u0435 glob) + .cursor/docs. \u0421\u0434\u0430\u0447\u0430: elcrm test."));
3826
4193
  }
3827
4194
 
3828
4195
  // src/commands/doctor.ts
3829
4196
  async function cmdDoctor(parsed) {
3830
- const cwd = resolve5(parsed.options.dir || process.cwd());
4197
+ const cwd = resolve9(parsed.options.dir || process.cwd());
3831
4198
  const dry = hasFlag(parsed, "dry");
3832
4199
  const doDocs = hasFlag(parsed, "docs");
3833
4200
  const doTest = hasFlag(parsed, "test");
@@ -3871,6 +4238,7 @@ async function cmdDoctor(parsed) {
3871
4238
  }
3872
4239
  }
3873
4240
  const tidy = await tidyFrontProject({ cwd, pkg, dry, hacks });
4241
+ await doctorKitSync({ cwd, dry });
3874
4242
  if (doDocs) {
3875
4243
  const flags = dry ? ["dry"] : [];
3876
4244
  const child = {
@@ -3903,7 +4271,7 @@ doctor: \u0433\u043E\u0442\u043E\u0432\u043E (${n} \u043F\u0440\u0430\u0432\u043
3903
4271
  }
3904
4272
 
3905
4273
  // src/commands/build.ts
3906
- import { existsSync as existsSync13 } from "fs";
4274
+ import { existsSync as existsSync16 } from "fs";
3907
4275
 
3908
4276
  // src/lib/nextVersion.ts
3909
4277
  async function nextVersion() {
@@ -3926,9 +4294,9 @@ async function nextVersion() {
3926
4294
 
3927
4295
  // src/commands/build.ts
3928
4296
  function resolveEntrypoint() {
3929
- if (existsSync13("./src/index.js"))
4297
+ if (existsSync16("./src/index.js"))
3930
4298
  return "./src/index.js";
3931
- if (existsSync13("./src/index.ts"))
4299
+ if (existsSync16("./src/index.ts"))
3932
4300
  return "./src/index.ts";
3933
4301
  return "./src/index.js";
3934
4302
  }
@@ -4006,22 +4374,22 @@ async function cmdUuid() {
4006
4374
  }
4007
4375
 
4008
4376
  // src/commands/postbuild.ts
4009
- import { resolve as resolve6 } from "path";
4377
+ import { resolve as resolve10 } from "path";
4010
4378
 
4011
4379
  // src/vite/postbuild.ts
4012
4380
  import {
4013
- existsSync as existsSync14,
4014
- readdirSync as readdirSync10,
4015
- readFileSync as readFileSync21,
4381
+ existsSync as existsSync17,
4382
+ readdirSync as readdirSync11,
4383
+ readFileSync as readFileSync25,
4016
4384
  rmSync,
4017
- statSync as statSync8,
4018
- writeFileSync as writeFileSync8
4385
+ statSync as statSync9,
4386
+ writeFileSync as writeFileSync10
4019
4387
  } from "fs";
4020
- import { join as join16 } from "path";
4388
+ import { join as join21 } from "path";
4021
4389
 
4022
4390
  // src/vite/concat-css.ts
4023
- import { readdirSync as readdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
4024
- import { join as join15 } from "path";
4391
+ import { readdirSync as readdirSync10, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
4392
+ import { join as join20 } from "path";
4025
4393
  var SKIP_CSS = new Set([
4026
4394
  "index.css",
4027
4395
  "tokens.css",
@@ -4036,11 +4404,11 @@ function concatDistCss(options) {
4036
4404
  outFile,
4037
4405
  ...options.skip ?? []
4038
4406
  ]);
4039
- const files = readdirSync9(options.distDir).filter((f) => f.endsWith(".css") && !skip.has(f)).sort();
4040
- const css = files.map((f) => readFileSync20(join15(options.distDir, f), "utf8")).join(`
4407
+ const files = readdirSync10(options.distDir).filter((f) => f.endsWith(".css") && !skip.has(f)).sort();
4408
+ const css = files.map((f) => readFileSync24(join20(options.distDir, f), "utf8")).join(`
4041
4409
  `);
4042
- const outPath = join15(options.distDir, outFile);
4043
- writeFileSync7(outPath, css);
4410
+ const outPath = join20(options.distDir, outFile);
4411
+ writeFileSync9(outPath, css);
4044
4412
  console.log(`[concat-css] ${files.length} \u0444\u0430\u0439\u043B\u043E\u0432 \u2192 ${outFile}`);
4045
4413
  return outPath;
4046
4414
  }
@@ -4057,19 +4425,19 @@ function postbuildLib(options) {
4057
4425
  distDir: dist,
4058
4426
  outFile: options.cssOutFile ?? "index.css"
4059
4427
  });
4060
- for (const name of readdirSync10(dist)) {
4061
- const dir = join16(dist, name);
4062
- if (!statSync8(dir).isDirectory())
4428
+ for (const name of readdirSync11(dist)) {
4429
+ const dir = join21(dist, name);
4430
+ if (!statSync9(dir).isDirectory())
4063
4431
  continue;
4064
4432
  if (keep.has(name))
4065
4433
  continue;
4066
- const impl = join16(dir, `${name}.d.ts`);
4067
- const nestedIndex = join16(dir, "index.d.ts");
4068
- const flat = join16(dist, `${name}.d.ts`);
4069
- const src = existsSync14(impl) ? impl : existsSync14(nestedIndex) ? nestedIndex : null;
4434
+ const impl = join21(dir, `${name}.d.ts`);
4435
+ const nestedIndex = join21(dir, "index.d.ts");
4436
+ const flat = join21(dist, `${name}.d.ts`);
4437
+ const src = existsSync17(impl) ? impl : existsSync17(nestedIndex) ? nestedIndex : null;
4070
4438
  if (src) {
4071
- const text = readFileSync21(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
4072
- writeFileSync8(flat, text);
4439
+ const text = readFileSync25(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
4440
+ writeFileSync10(flat, text);
4073
4441
  console.log(`[postbuild] types \u2192 ${name}.d.ts`);
4074
4442
  }
4075
4443
  rmSync(dir, { recursive: true, force: true });
@@ -4080,7 +4448,7 @@ function postbuildLib(options) {
4080
4448
  // src/commands/postbuild.ts
4081
4449
  async function cmdPostbuild(parsed) {
4082
4450
  const dir = parsed.options.dir || "./dist";
4083
- const distDir = resolve6(process.cwd(), dir);
4451
+ const distDir = resolve10(process.cwd(), dir);
4084
4452
  postbuildLib({ distDir });
4085
4453
  console.log(`[postbuild] \u0433\u043E\u0442\u043E\u0432\u043E: ${distDir}`);
4086
4454
  }
@@ -4148,8 +4516,8 @@ css-sanitize: \u043F\u043E\u0447\u0438\u043D\u0435\u043D\u043E ${cleaned.length}
4148
4516
  }
4149
4517
 
4150
4518
  // src/commands/audit.ts
4151
- import { existsSync as existsSync15, readFileSync as readFileSync22 } from "fs";
4152
- import { join as join17, relative as relative4 } from "path";
4519
+ import { existsSync as existsSync18, readFileSync as readFileSync26 } from "fs";
4520
+ import { join as join22, relative as relative4 } from "path";
4153
4521
  function scan(cwd) {
4154
4522
  const out = [];
4155
4523
  const pkg = readPackageJson(cwd);
@@ -4180,7 +4548,7 @@ function scan(cwd) {
4180
4548
  }
4181
4549
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
4182
4550
  for (const file of files) {
4183
- const text = readFileSync22(file, "utf8");
4551
+ const text = readFileSync26(file, "utf8");
4184
4552
  const rel2 = relative4(cwd, file);
4185
4553
  const lines = text.split(/\r?\n/);
4186
4554
  lines.forEach((line, i) => {
@@ -4282,7 +4650,7 @@ function scan(cwd) {
4282
4650
  return false;
4283
4651
  if (/theme(-light|-dark)?\.css$/.test(f))
4284
4652
  return false;
4285
- const t = readFileSync22(f, "utf8");
4653
+ const t = readFileSync26(f, "utf8");
4286
4654
  return t.includes("--field-border:") && t.includes("--button-secondary-bg:") && t.includes("transparent");
4287
4655
  });
4288
4656
  if (toolbarMarkers.length > 1) {
@@ -4293,8 +4661,8 @@ function scan(cwd) {
4293
4661
  fix: "\u043E\u0431\u0449\u0438\u0439 \u043A\u043B\u0430\u0441\u0441 + theme tokens"
4294
4662
  });
4295
4663
  }
4296
- const styleDir = ["src/style", "src/styles", "style"].map((d) => join17(cwd, d)).find(existsSync15);
4297
- if (styleDir && !existsSync15(join17(styleDir, "elcrm.css"))) {
4664
+ const styleDir = ["src/style", "src/styles", "style"].map((d) => join22(cwd, d)).find(existsSync18);
4665
+ if (styleDir && !existsSync18(join22(styleDir, "elcrm.css"))) {
4298
4666
  out.push({
4299
4667
  level: "info",
4300
4668
  code: "elcrm-css",
@@ -4302,8 +4670,8 @@ function scan(cwd) {
4302
4670
  fix: "elcrm css"
4303
4671
  });
4304
4672
  }
4305
- if (styleDir && existsSync15(join17(styleDir, "elcrm.css"))) {
4306
- const elcrmCss = readFileSync22(join17(styleDir, "elcrm.css"), "utf8");
4673
+ if (styleDir && existsSync18(join22(styleDir, "elcrm.css"))) {
4674
+ const elcrmCss = readFileSync26(join22(styleDir, "elcrm.css"), "utf8");
4307
4675
  for (const m of elcrmCss.matchAll(/@import\s+["'](@elcrm\/[^"']+)["']/g)) {
4308
4676
  const spec = m[1];
4309
4677
  if (!resolveElcrmCssImport(spec, cwd)) {
@@ -4311,7 +4679,7 @@ function scan(cwd) {
4311
4679
  level: "error",
4312
4680
  code: "elcrm-import-missing",
4313
4681
  message: `\u0431\u0438\u0442\u044B\u0439 @import "${spec}" \u2014 \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442 \u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u043C \u043F\u0430\u043A\u0435\u0442\u0435`,
4314
- file: relative4(cwd, join17(styleDir, "elcrm.css")),
4682
+ file: relative4(cwd, join22(styleDir, "elcrm.css")),
4315
4683
  fix: "elcrm css"
4316
4684
  });
4317
4685
  }
@@ -4323,8 +4691,8 @@ function scan(cwd) {
4323
4691
  "theme-light.css",
4324
4692
  "theme-dark.css",
4325
4693
  "theme.css"
4326
- ].map((f) => join17(styleDir, f));
4327
- const themeText = themeFiles.filter(existsSync15).map((f) => readFileSync22(f, "utf8")).join(`
4694
+ ].map((f) => join22(styleDir, f));
4695
+ const themeText = themeFiles.filter(existsSync18).map((f) => readFileSync26(f, "utf8")).join(`
4328
4696
  `);
4329
4697
  if (themeText && !/--popup-shadow\s*:/.test(themeText) && /--date-popup-shadow\s*:|--select-background\s*:/.test(themeText)) {
4330
4698
  out.push({
@@ -4346,7 +4714,7 @@ function scan(cwd) {
4346
4714
  for (const file of files) {
4347
4715
  if (!file.endsWith(".css"))
4348
4716
  continue;
4349
- const text = readFileSync22(file, "utf8");
4717
+ const text = readFileSync26(file, "utf8");
4350
4718
  const relPath = relative4(cwd, file);
4351
4719
  const lines = text.split(/\r?\n/);
4352
4720
  let prevNonEmpty = "";
@@ -4435,15 +4803,89 @@ ${c.cyan("\u2192")} elcrm migrate front${hasFlag(parsed, "dry") ? " --dry" : ""}
4435
4803
  console.log("\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm css && elcrm doctor");
4436
4804
  }
4437
4805
 
4806
+ // src/commands/kit.ts
4807
+ import { resolve as resolve11 } from "path";
4808
+ function showKitHelp() {
4809
+ console.log(`elcrm kit \u2014 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F elUI (UIKit)
4810
+
4811
+ emit [--dir=\u2026] [--dry]
4812
+ dist/*.d.ts \u2192 .elcrm/kit-manifest.json + dist/kit-manifest.json
4813
+ \u0417\u0430\u043F\u0443\u0441\u043A\u0430\u0442\u044C \u0432 @elcrm/components | @elcrm/form \u043F\u043E\u0441\u043B\u0435 build
4814
+
4815
+ sync [--api=\u2026] [--web=\u2026] [--no-reseed] [--dry]
4816
+ manifest \u2192 elUI.api/src/kit/manifests/*.json
4817
+ \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0435\u0442 pkg-versions + elUI.web deps + kit:reseed
4818
+
4819
+ pull [--api=\u2026] [--no-reseed] [--dry]
4820
+ emit + sync (\u0443\u0434\u043E\u0431\u043D\u043E \u0432 postbuild)
4821
+
4822
+ \u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435: ELUI_API_DIR, ELUI_WEB_DIR
4823
+
4824
+ \u041F\u0440\u0438\u043C\u0435\u0440 (components):
4825
+ npm run build && elcrm kit pull
4826
+
4827
+ \u041F\u0440\u0438\u043C\u0435\u0440 (elUI.api \u043F\u043E\u0441\u043B\u0435 pull):
4828
+ bun run kit:reseed
4829
+ `);
4830
+ }
4831
+ async function cmdKit(parsed) {
4832
+ const sub = parsed.positionals[0] || "help";
4833
+ const cwd = resolve11(parsed.options.dir || process.cwd());
4834
+ const dry = hasFlag(parsed, "dry");
4835
+ const noReseed = hasFlag(parsed, "no-reseed");
4836
+ if (sub === "help" || sub === "--help") {
4837
+ showKitHelp();
4838
+ return;
4839
+ }
4840
+ if (sub === "emit") {
4841
+ const pkg = readPkgAt(cwd);
4842
+ if (!pkg?.name || !kitPkgSlug(pkg.name)) {
4843
+ console.error(c.red("kit emit \u2014 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439\u0442\u0435 \u0432 @elcrm/components \u0438\u043B\u0438 @elcrm/form"));
4844
+ process.exit(1);
4845
+ }
4846
+ const manifest = emitKitManifest(cwd);
4847
+ const paths = writeKitManifest(cwd, manifest, dry);
4848
+ console.log(`${dry ? "[dry] " : ""}emit: ${manifest.components.length} \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432`);
4849
+ for (const p of paths)
4850
+ console.log(` \u2192 ${p}`);
4851
+ return;
4852
+ }
4853
+ if (sub === "sync") {
4854
+ await syncKitManifest({
4855
+ cwd,
4856
+ apiDir: parsed.options.api,
4857
+ webDir: parsed.options.web,
4858
+ dry,
4859
+ reseed: !noReseed
4860
+ });
4861
+ return;
4862
+ }
4863
+ if (sub === "pull") {
4864
+ if (!isElcrmLibPackage(cwd)) {
4865
+ console.error(c.red("kit pull \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 npm-\u043F\u0430\u043A\u0435\u0442\u0435 @elcrm/components | @elcrm/form"));
4866
+ process.exit(1);
4867
+ }
4868
+ await emitAndSync({
4869
+ cwd,
4870
+ dry,
4871
+ reseed: !noReseed
4872
+ });
4873
+ return;
4874
+ }
4875
+ console.error(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043F\u043E\u0434\u043A\u043E\u043C\u0430\u043D\u0434\u0430 kit: ${sub}`);
4876
+ showKitHelp();
4877
+ process.exit(1);
4878
+ }
4879
+
4438
4880
  // src/index.ts
4439
4881
  function cliVersion() {
4440
4882
  const here = dirname4(fileURLToPath2(import.meta.url));
4441
4883
  for (const p of [
4442
- join18(here, "..", "package.json"),
4443
- join18(here, "package.json")
4884
+ join23(here, "..", "package.json"),
4885
+ join23(here, "package.json")
4444
4886
  ]) {
4445
4887
  try {
4446
- const v = JSON.parse(readFileSync23(p, "utf8")).version;
4888
+ const v = JSON.parse(readFileSync27(p, "utf8")).version;
4447
4889
  if (typeof v === "string" && v)
4448
4890
  return v;
4449
4891
  } catch {}
@@ -4479,6 +4921,11 @@ function showHelp() {
4479
4921
  .cursor/rules + .cursor/docs + AGENTS.md
4480
4922
  --lib: \u0447\u0435\u043A\u043B\u0438\u0441\u0442 \u0430\u0432\u0442\u043E\u0440\u0430 @elcrm/*
4481
4923
 
4924
+ kit <emit|sync|pull> [--api=\u2026] [--web=\u2026] [--no-reseed] [--dry]
4925
+ emit \u2014 manifest \u0438\u0437 dist/*.d.ts (@elcrm/components|form)
4926
+ sync \u2014 manifest \u2192 elUI.api/src/kit/manifests + reseed
4927
+ pull \u2014 emit + sync (postbuild \u043F\u0430\u043A\u0435\u0442\u0430)
4928
+
4482
4929
  migrate <name> [--dry] | migrate --list
4483
4930
  front|ui = \u043F\u0430\u043A\u0435\u0442 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0439 (aliases, form-native, native-html, tokens, \u2026)
4484
4931
  \u043F\u043E \u043E\u0434\u043D\u043E\u0439: form-aliases, form-native, native-html, form-tokens, components,
@@ -4490,6 +4937,7 @@ function showHelp() {
4490
4937
  \u041F\u043E\u0441\u043B\u0435 --fix \u0434\u043B\u044F imports: elcrm css
4491
4938
 
4492
4939
  doctor [--dry] [--docs] [--test] [--hacks] [--dir=\u2026]
4940
+ migrate + css; \u0432 @elcrm/components|form + dist/ \u2192 kit pull
4493
4941
  \u041D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u043F\u043E\u0440\u044F\u0434\u043A\u0430 (\u043F\u0440\u043E\u0441\u0442\u043E \u0432\u044B\u0437\u0432\u0430\u0442\u044C). \u041F\u0440\u0435\u0432\u044C\u044E: --dry
4494
4942
  package.json \u2192 migrate front \u2192 css. --docs / --test \u043F\u043E \u0436\u0435\u043B\u0430\u043D\u0438\u044E
4495
4943
 
@@ -4563,6 +5011,9 @@ async function main() {
4563
5011
  case "cursor":
4564
5012
  await cmdCursor(parsed);
4565
5013
  break;
5014
+ case "kit":
5015
+ await cmdKit(parsed);
5016
+ break;
4566
5017
  case "uuid":
4567
5018
  await cmdUuid();
4568
5019
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elcrm",
3
- "version": "1.1.18",
4
- "description": "CLI @elcrm/*: doctor --fix (порядок проекта), update, css, docs, cursor, migrate",
3
+ "version": "1.1.20",
4
+ "description": "CLI @elcrm/*: doctor --fix (порядок проекта), update, css, docs, cursor, kit, migrate",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "MaSkal <dev@elcrm.online>",
@@ -52,7 +52,7 @@
52
52
  "build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm && bun build ./src/vite/plugin-css-scoped.ts --outfile ./dist/vite/plugin-css-scoped.js --target node --format esm --packages external && bun build ./src/vite/discover-lib.ts --outfile ./dist/vite/discover-lib.js --target node --format esm --packages external && bun build ./src/vite/concat-css.ts --outfile ./dist/vite/concat-css.js --target node --format esm --packages external && bun build ./src/vite/check-tokens.ts --outfile ./dist/vite/check-tokens.js --target node --format esm --packages external && bun build ./src/vite/postbuild.ts --outfile ./dist/vite/postbuild.js --target node --format esm --packages external && bun build ./src/vite/minify-css-vars.ts --outfile ./dist/vite/minify-css-vars.js --target node --format esm --packages external && node ./scripts/chmod-bin.mjs && node ./scripts/copy-vite-dts.mjs",
53
53
  "dev": "bun run ./src/index.ts",
54
54
  "elcrm": "bun run ./src/index.ts",
55
- "test": "bun test src/lib/migrate src/lib/tidy.test.ts src/vite",
55
+ "test": "bun test src/lib/migrate src/lib/tidy.test.ts src/lib/kit src/vite",
56
56
  "link:local": "bun run build && bun link",
57
57
  "prepublishOnly": "bun run test && bun run build",
58
58
  "publish:npm": "npm publish"
@@ -159,6 +159,11 @@
159
159
  --panel-info-backdrop: color-mix(in srgb, var(--shell-color) 35%, transparent);
160
160
  --healthmap-cell: 12px;
161
161
  --healthmap-gap: 3px;
162
+ --healthmap-l0: var(--shell-background-muted);
163
+ --healthmap-l1: color-mix(in srgb, var(--shell-color-success) 25%, var(--shell-background-muted));
164
+ --healthmap-l2: color-mix(in srgb, var(--shell-color-success) 45%, transparent);
165
+ --healthmap-l3: color-mix(in srgb, var(--shell-color-success) 70%, transparent);
166
+ --healthmap-l4: var(--shell-color-success);
162
167
  }
163
168
 
164
169
  /* —— база приложения (без захардкоженных цветов) —— */
@@ -170,6 +170,11 @@
170
170
  --panel-info-backdrop: color-mix(in srgb, var(--shell-color) 35%, transparent);
171
171
  --healthmap-cell: 12px;
172
172
  --healthmap-gap: 3px;
173
+ --healthmap-l0: var(--shell-background-muted);
174
+ --healthmap-l1: color-mix(in srgb, var(--shell-color-success) 25%, var(--shell-background-muted));
175
+ --healthmap-l2: color-mix(in srgb, var(--shell-color-success) 45%, transparent);
176
+ --healthmap-l3: color-mix(in srgb, var(--shell-color-success) 70%, transparent);
177
+ --healthmap-l4: var(--shell-color-success);
173
178
  }
174
179
 
175
180
  /* —— база приложения (без захардкоженных цветов) —— */