cascivo 0.3.6 → 0.4.1
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.mjs
CHANGED
|
@@ -200,14 +200,14 @@ function resolveTemplateTarget(cwd, target) {
|
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Names from other libraries → the cascivo layout primitive that fills the same
|
|
203
|
-
* role, so `cascivo add
|
|
204
|
-
* resolve (e.g. `
|
|
203
|
+
* role, so `cascivo add box` installs `layout/flex`. Bare names that already
|
|
204
|
+
* resolve (e.g. `flex` → `layout/flex` via suffix match) still list an alias here
|
|
205
|
+
* for discoverability.
|
|
205
206
|
*/
|
|
206
207
|
const LAYOUT_ALIASES = {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
vstack: "stack",
|
|
208
|
+
box: "layout/flex",
|
|
209
|
+
hstack: "layout/flex",
|
|
210
|
+
vstack: "layout/flex",
|
|
211
211
|
gap: "spacer"
|
|
212
212
|
};
|
|
213
213
|
/**
|
|
@@ -526,7 +526,9 @@ function indexHtml(opts) {
|
|
|
526
526
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
527
527
|
<title>${opts.name}</title>
|
|
528
528
|
<style>
|
|
529
|
-
@layer cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component, cascivo.theme, cascivo.override;
|
|
529
|
+
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component, cascivo.theme, cascivo.blocks, cascivo.override;
|
|
530
|
+
/* Third-party CSS goes in the low-priority vendor layer so it can't beat cascivo:
|
|
531
|
+
@import url('some-lib/styles.css') layer(vendor); — see docs/THIRD-PARTY-CSS.md */
|
|
530
532
|
@layer cascivo.reset {
|
|
531
533
|
*,
|
|
532
534
|
*::before,
|
|
@@ -687,6 +689,54 @@ npm run dev
|
|
|
687
689
|
Add more components with \`npx cascivo add <component>\`.
|
|
688
690
|
`;
|
|
689
691
|
}
|
|
692
|
+
function agentsMd(opts) {
|
|
693
|
+
return `# Agent instructions — ${opts.name}
|
|
694
|
+
|
|
695
|
+
This is a [cascivo](https://cascivo.com) app. When generating or editing CSS, follow
|
|
696
|
+
the **layer contract** — cascivo styles live in cascade layers, and layer order beats
|
|
697
|
+
selector specificity.
|
|
698
|
+
|
|
699
|
+
## CSS layer contract
|
|
700
|
+
|
|
701
|
+
1. Every declaration goes inside an \`@layer\` block. Unlayered CSS beats all layers
|
|
702
|
+
regardless of specificity — never emit it.
|
|
703
|
+
2. Never invent layer names. Write only: your app slot \`cascivo.example\` (declared in
|
|
704
|
+
the order statement in \`index.html\`) for page styles, and
|
|
705
|
+
\`@layer cascivo.override { … }\` for hotfixes / one-off overrides — it beats
|
|
706
|
+
everything cascivo ships.
|
|
707
|
+
3. Never nest layers deeper than the shipped \`cascivo.blocks.<name>\` pattern. For
|
|
708
|
+
sub-elements, use native CSS nesting inside one layer block, not new sublayers.
|
|
709
|
+
4. Third-party CSS: \`@import url('lib/styles.css') layer(vendor);\` with \`vendor\`
|
|
710
|
+
declared before the cascivo layers. Don't import vendor CSS from JavaScript — route it
|
|
711
|
+
through a CSS file, or use \`@cascivo/vite-plugin\` (\`cascivoLayers\`) to layer it.
|
|
712
|
+
5. Style with \`--cascivo-*\` tokens, not raw values.
|
|
713
|
+
|
|
714
|
+
This app's declared layer order (in \`index.html\`):
|
|
715
|
+
|
|
716
|
+
\`\`\`css
|
|
717
|
+
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
|
|
718
|
+
cascivo.theme, cascivo.blocks, cascivo.override;
|
|
719
|
+
\`\`\`
|
|
720
|
+
|
|
721
|
+
### Worked example — nesting, not new layers
|
|
722
|
+
|
|
723
|
+
\`\`\`css
|
|
724
|
+
@layer cascivo.override {
|
|
725
|
+
.projectCard {
|
|
726
|
+
/* Sub-elements nest inside the one block — no cascivo.card.status sublayer. */
|
|
727
|
+
.statusBadge {
|
|
728
|
+
color: var(--cascivo-color-text-subtle);
|
|
729
|
+
.pulseDot {
|
|
730
|
+
background: var(--cascivo-color-success);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
\`\`\`
|
|
736
|
+
|
|
737
|
+
More: cascivo's machine-readable guide is at https://cascivo.com/llms.txt.
|
|
738
|
+
`;
|
|
739
|
+
}
|
|
690
740
|
/** Build the full set of files for a new cascivo app. Pure — no filesystem I/O. */
|
|
691
741
|
function buildScaffold(opts) {
|
|
692
742
|
const sections = resolveSections(opts.sections);
|
|
@@ -719,6 +769,10 @@ function buildScaffold(opts) {
|
|
|
719
769
|
path: "README.md",
|
|
720
770
|
contents: readme(opts)
|
|
721
771
|
},
|
|
772
|
+
{
|
|
773
|
+
path: "AGENTS.md",
|
|
774
|
+
contents: agentsMd(opts)
|
|
775
|
+
},
|
|
722
776
|
{
|
|
723
777
|
path: "src/main.tsx",
|
|
724
778
|
contents: mainTsx()
|
|
@@ -888,6 +942,10 @@ async function runDoctor(root) {
|
|
|
888
942
|
function isTranslationRef(v) {
|
|
889
943
|
return typeof v === "object" && v !== null && "$t" in v;
|
|
890
944
|
}
|
|
945
|
+
/** A JS literal for a state initial value. */
|
|
946
|
+
function serializeInitial(value) {
|
|
947
|
+
return typeof value === "string" ? `'${value.replace(/'/g, "\\'")}'` : String(value);
|
|
948
|
+
}
|
|
891
949
|
function serializeProp(value) {
|
|
892
950
|
if (typeof value === "string") return `"${value.replace(/"/g, "\\\"")}"`;
|
|
893
951
|
if (typeof value === "number" || typeof value === "boolean") return `{${value}}`;
|
|
@@ -907,14 +965,15 @@ function renderNode(node, indent, _parentPropNames, knownComponents) {
|
|
|
907
965
|
knownComponents.add(compName);
|
|
908
966
|
const propParts = [];
|
|
909
967
|
if (node.props) for (const [k, v] of Object.entries(node.props)) propParts.push(`${k}=${serializeProp(v)}`);
|
|
910
|
-
if (node.bind) for (const [k, ref] of Object.entries(node.bind)) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
968
|
+
if (node.bind) for (const [k, ref] of Object.entries(node.bind)) if (ref.startsWith("$state.")) propParts.push(`${k}={${ref.slice(7)}.value}`);
|
|
969
|
+
else propParts.push(`${k}={data.${ref.replace(/^\$data\./, "")}}`);
|
|
970
|
+
if (node.events) for (const [k, ref] of Object.entries(node.events)) if (ref.startsWith("$state.set.")) {
|
|
971
|
+
const key = ref.slice(11);
|
|
972
|
+
propParts.push(`${k}={(e) => (${key}.value = coerceValue(e))}`);
|
|
973
|
+
} else if (ref.startsWith("$state.toggle.")) {
|
|
974
|
+
const key = ref.slice(14);
|
|
975
|
+
propParts.push(`${k}={() => (${key}.value = !${key}.value)}`);
|
|
976
|
+
} else propParts.push(`${k}={actions.${ref.replace(/^\$actions\./, "")}}`);
|
|
918
977
|
const propsStr = propParts.length > 0 ? " " + propParts.join(" ") : "";
|
|
919
978
|
const childrenStr = node.children !== void 0 ? renderChildren(node.children, indent, [], knownComponents) : void 0;
|
|
920
979
|
if (childrenStr !== void 0 && childrenStr !== "") return `${indent}<${compName}${propsStr}>\n${indent} ${childrenStr}\n${indent}</${compName}>`;
|
|
@@ -925,10 +984,12 @@ function collectBindAndEvents(nodes) {
|
|
|
925
984
|
const actionProps = [];
|
|
926
985
|
function walk(node) {
|
|
927
986
|
if (node.bind) for (const ref of Object.values(node.bind)) {
|
|
987
|
+
if (ref.startsWith("$state.")) continue;
|
|
928
988
|
const path = ref.replace(/^\$data\./, "");
|
|
929
989
|
if (!boundProps.includes(path)) boundProps.push(path);
|
|
930
990
|
}
|
|
931
991
|
if (node.events) for (const ref of Object.values(node.events)) {
|
|
992
|
+
if (ref.startsWith("$state.")) continue;
|
|
932
993
|
const name = ref.replace(/^\$actions\./, "");
|
|
933
994
|
if (!actionProps.includes(name)) actionProps.push(name);
|
|
934
995
|
}
|
|
@@ -940,9 +1001,21 @@ function collectBindAndEvents(nodes) {
|
|
|
940
1001
|
actionProps
|
|
941
1002
|
};
|
|
942
1003
|
}
|
|
1004
|
+
/** True if any node writes state via "$state.set.*" (needs the coerceValue helper). */
|
|
1005
|
+
function usesStateSetter(nodes) {
|
|
1006
|
+
const walk = (node) => {
|
|
1007
|
+
if (node.events && Object.values(node.events).some((r) => r.startsWith("$state.set."))) return true;
|
|
1008
|
+
return Array.isArray(node.children) ? node.children.some(walk) : false;
|
|
1009
|
+
};
|
|
1010
|
+
return nodes.some(walk);
|
|
1011
|
+
}
|
|
943
1012
|
function generateTsx(config, _propMetas, componentsPath) {
|
|
944
1013
|
const knownComponents = /* @__PURE__ */ new Set();
|
|
945
|
-
const
|
|
1014
|
+
const allNodes = Object.values(config.view.regions).flat();
|
|
1015
|
+
const { boundProps, actionProps } = collectBindAndEvents(allNodes);
|
|
1016
|
+
const stateEntries = Object.entries(config.state ?? {});
|
|
1017
|
+
const hasState = stateEntries.length > 0;
|
|
1018
|
+
const needsCoerce = usesStateSetter(allNodes);
|
|
946
1019
|
const regionBlocks = Object.entries(config.view.regions).map(([regionName, nodes]) => {
|
|
947
1020
|
return ` <div className="region-${regionName}">\n${nodes.map((n) => renderNode(n, " ", [], knownComponents)).join("\n")}\n </div>`;
|
|
948
1021
|
}).join("\n");
|
|
@@ -950,13 +1023,24 @@ function generateTsx(config, _propMetas, componentsPath) {
|
|
|
950
1023
|
const hasData = boundProps.length > 0;
|
|
951
1024
|
const hasActions = actionProps.length > 0;
|
|
952
1025
|
const params = [hasData ? `data: {\n${boundProps.map((p) => ` ${p.replace(/\./g, "_")}: unknown // TODO: type your data`).join("\n")}\n }` : "", hasActions ? `actions: {\n${actionProps.map((a) => ` ${a}: (...args: unknown[]) => unknown`).join("\n")}\n }` : ""].filter(Boolean).join(",\n ");
|
|
953
|
-
|
|
1026
|
+
const propsType = params ? `{\n ${params}\n}` : "{}";
|
|
1027
|
+
const coreImport = hasState ? "\nimport { useSignal, useSignals } from '@cascivo/core'" : "";
|
|
1028
|
+
const coerceHelper = needsCoerce ? `
|
|
1029
|
+
/** Unwrap a DOM event into the value a state setter should store. */
|
|
1030
|
+
function coerceValue(e: unknown) {
|
|
1031
|
+
const t = (e as { target?: { type?: string; checked?: boolean; value?: unknown } })?.target
|
|
1032
|
+
if (t) return t.type === 'checkbox' || t.type === 'radio' ? t.checked : t.value
|
|
1033
|
+
return e
|
|
1034
|
+
}
|
|
1035
|
+
` : "";
|
|
1036
|
+
const stateDecls = hasState ? " useSignals()\n" + stateEntries.map(([k, v]) => ` const ${k} = useSignal(${serializeInitial(v)})`).join("\n") + "\n" : "";
|
|
1037
|
+
return `import React from 'react'${coreImport}
|
|
954
1038
|
${importLines}
|
|
955
|
-
|
|
956
|
-
interface PageProps ${
|
|
1039
|
+
${coerceHelper}
|
|
1040
|
+
interface PageProps ${propsType}
|
|
957
1041
|
|
|
958
1042
|
export function GeneratedPage({ ${hasData ? "data, " : ""}${hasActions ? "actions" : ""} }: PageProps) {
|
|
959
|
-
return (
|
|
1043
|
+
${stateDecls} return (
|
|
960
1044
|
<div className="cascade-view">
|
|
961
1045
|
${regionBlocks}
|
|
962
1046
|
</div>
|
|
@@ -1112,7 +1196,7 @@ async function list(config, options = {}) {
|
|
|
1112
1196
|
return existsSync(join(process.cwd(), config.outputDir, outputName));
|
|
1113
1197
|
});
|
|
1114
1198
|
console.log(formatList(components));
|
|
1115
|
-
console.log("\nTip: install any entry by its bare name — `cascivo add
|
|
1199
|
+
console.log("\nTip: install any entry by its bare name — `cascivo add flex` resolves to `layout/flex`. Layout primitives (Flex, Grid, AutoGrid, Columns, Spacer, Center) replace inline-style layout; see docs/cookbooks/layout-and-spacing.md.");
|
|
1116
1200
|
}
|
|
1117
1201
|
//#endregion
|
|
1118
1202
|
//#region src/commands/registry.ts
|
|
@@ -2131,7 +2215,7 @@ async function run(args) {
|
|
|
2131
2215
|
break;
|
|
2132
2216
|
}
|
|
2133
2217
|
case "audit": {
|
|
2134
|
-
const { audit } = await import("./audit-
|
|
2218
|
+
const { audit } = await import("./audit-g0Ac-Uzl.mjs");
|
|
2135
2219
|
await audit(rest, await loadConfig());
|
|
2136
2220
|
break;
|
|
2137
2221
|
}
|