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 flex` installs `layout/stack`. Bare names that already
204
- * resolve (e.g. `stack` → `layout/stack` via suffix match) need no alias.
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
- flex: "stack",
208
- box: "stack",
209
- hstack: "stack",
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
- const path = ref.replace(/^\$data\./, "").replace(/\./g, ".");
912
- propParts.push(`${k}={data.${path}}`);
913
- }
914
- if (node.events) for (const [k, ref] of Object.entries(node.events)) {
915
- const actionName = ref.replace(/^\$actions\./, "");
916
- propParts.push(`${k}={actions.${actionName}}`);
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 { boundProps, actionProps } = collectBindAndEvents(Object.values(config.view.regions).flat());
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
- return `import React from 'react'
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 ${params ? `{\n ${params}\n}` : "{}"}
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 stack` resolves to `layout/stack`. Layout primitives (Stack, Grid, AutoGrid, Columns, Spacer, Center) replace inline-style layout; see docs/cookbooks/layout-and-spacing.md.");
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-InQqdY1s.mjs");
2218
+ const { audit } = await import("./audit-g0Ac-Uzl.mjs");
2135
2219
  await audit(rest, await loadConfig());
2136
2220
  break;
2137
2221
  }