@exxatdesignux/ui 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `create-exxat-app` now drops the full Cursor + Claude agent bundle into
8
+ the scaffolded repo **automatically** after the dependency install.
9
+ Before this change, a new app shipped with only the 11 binding rules
10
+ that happened to live under `apps/web/.cursor/rules/`; the full curated
11
+ set (29 rules · 16 Cursor skills · 16 Claude skills · 6 pattern docs ·
12
+ 4 handbook files) required a second manual `exxat-ui sync-extras` step.
13
+
14
+ What's new for a fresh `pnpm create exxat-app my-app`:
15
+ - After `pnpm install` succeeds, `init.mjs` invokes `sync-extras` in
16
+ `--quiet` mode so the scaffolded repo is one step from "open in
17
+ Cursor and every rule + skill is live".
18
+ - The bundle is namespaced (`exxat-*` files only) so re-syncing on
19
+ upgrade never touches a consumer's own rules, skills, AGENTS.md, or
20
+ product code.
21
+ - Opt out with `create-exxat-app my-app --no-extras` (alias
22
+ `--skip-extras`) — print a "run sync-extras later" hint instead.
23
+ - Final "Done!" message no longer treats `sync-extras` as an optional
24
+ follow-up; it mentions the upgrade-time refresh command instead.
25
+
26
+ Smaller polish in the same release:
27
+ - `--help` now documents `--no-extras`.
28
+ - Project-name normalization handles absolute target paths cleanly
29
+ (no leading-hyphen package names; no `.//absolute/path/` in the
30
+ success message).
31
+ - The `cd <target>` hint is suppressed when the user scaffolded into
32
+ the current directory with `.`.
33
+ - `sync-extras.mjs` learned `--quiet` (and the
34
+ `EXXAT_SYNC_EXTRAS_QUIET=1` env override) so the scaffolder can
35
+ call it without flooding the user with 50+ per-file log lines; the
36
+ final summary line still prints.
37
+ - README updated to describe the auto-extras flow and to clarify the
38
+ `sync-extras` command is now an _upgrade_ refresh, not a required
39
+ first step.
40
+
41
+ ## 0.4.2
42
+
43
+ ### Patch Changes
44
+
45
+ - ### `HubTable` — stop warning when the centralized default covers a supported view
46
+
47
+ The dev-time `Missing renderer for supported view …` warning in
48
+ `HubTable` only treated `data-table` as having a built-in fallback,
49
+ even though the same component synthesises centralized defaults for
50
+ `list-with-toolbar` (when the consumer passes `renderListRow`) and
51
+ `board-with-toolbar` (when the consumer passes `renderBoardCard` plus
52
+ `boardGroups`). Every consumer that wired list/board the happy-path
53
+ way saw a spurious console warning every time their hub mounted —
54
+ including the freshly-scaffolded Library hub in `create-exxat-app`,
55
+ where the first thing a new user saw was a repeating
56
+ `[HubTable: Library] Missing renderer for supported view "list"
57
+ (list-with-toolbar)` line, even though the view renders correctly.
58
+
59
+ The warning loop now also skips the two centralized-default cases
60
+ (it still fires for exotic surfaces that genuinely need an explicit
61
+ renderer entry, e.g. Placements' per-phase column-search board, or a
62
+ custom finder body the package doesn't synthesise).
63
+
64
+ No behavior change in production builds (the warning is already gated
65
+ on `process.env.NODE_ENV !== "production"`).
66
+
3
67
  ## 0.4.1
4
68
 
5
69
  ### Patch Changes
package/README.md CHANGED
@@ -26,8 +26,23 @@ bun create exxat-app my-app
26
26
 
27
27
  The scaffolder ships a complete Next.js 16 starter: the reference
28
28
  **Library** hub at `/library/all`, the `/columns` cell-catalog showcase,
29
- typed mock data, the full Cursor skill + pattern doc set under
30
- `cursor-rules/`, `cursor-skills/`, `patterns/`, and `handbook/`.
29
+ typed mock data, plus the full Cursor + Claude agent bundle dropped into
30
+ the right paths **automatically**:
31
+
32
+ - `.cursor/rules/exxat-*.mdc` — 29 binding rules (Cursor reads these as
33
+ always-on / agent-requestable).
34
+ - `.cursor/skills/exxat-*/` and `.claude/skills/exxat-*/` — 16 skill
35
+ bundles (`SKILL.md` + references); same source written to both readers.
36
+ - `docs/exxat-ds/*.md` — 6 pattern docs (data views, command menu,
37
+ drawer-vs-dialog, KPI flat band, …) + the consumer upgrade checklist.
38
+ - `docs/exxat-ds/handbook/*.md` — HANDBOOK · glossary · voice-and-tone ·
39
+ reference-implementations.
40
+ - `AGENTS.md` (root) — the project's AI handbook from the reference app.
41
+
42
+ When you `cd my-app` and open Cursor for the first time, every binding
43
+ rule, skill, and pattern doc is already in place. To opt out, pass
44
+ `--no-extras` to the scaffolder; you can run it later with
45
+ `exxat-ui sync-extras` (see below).
31
46
 
32
47
  ## Adding to an existing Next.js app
33
48
 
@@ -55,19 +70,21 @@ Mount `KeyMetricsProvider` near the root if you want the "Ask Leo about
55
70
  these metrics" CTA wired to your AI surface (otherwise the strip just
56
71
  hides that button automatically).
57
72
 
58
- ## Refresh Cursor skills + pattern docs in any consumer
73
+ ## Refresh Cursor / Claude agent bundle in any consumer
59
74
 
60
- The package ships a parallel set of binding rules, skills, and pattern
61
- docs so AI agents in your own repo can read the same guidance the source
62
- repo uses. To install or refresh them in your repo:
75
+ `pnpm create exxat-app` drops the agent bundle for you on first scaffold.
76
+ When you upgrade `@exxatdesignux/ui` later or when you want to seed the
77
+ bundle into an **existing** Next.js app that already depends on the DS
78
+ run:
63
79
 
64
80
  ```bash
65
81
  pnpm dlx --package=@exxatdesignux/ui@latest exxat-ui sync-extras
66
82
  ```
67
83
 
68
- This copies `cursor-rules/`, `cursor-skills/`, `patterns/`, and
69
- `handbook/` into your repo root. The next time Cursor opens, every
70
- binding rule + skill is live.
84
+ The command is **namespaced**: it only writes files starting with
85
+ `exxat-` (rules + skills) and only inside `.cursor/`, `.claude/skills/`,
86
+ and `docs/exxat-ds/`. Your own rules, skills, AGENTS.md, and product
87
+ code are never touched.
71
88
 
72
89
  ## What's inside
73
90
 
@@ -121,8 +138,8 @@ natively.
121
138
  custom property the DS ships.
122
139
 
123
140
  The full doc set is mirrored under `handbook/` and `patterns/` in this
124
- package; `exxat-ui sync-extras` drops them into your repo so they sit
125
- alongside your source.
141
+ package; `pnpm create exxat-app` drops them at `docs/exxat-ds/` on first
142
+ scaffold, and `exxat-ui sync-extras` re-syncs them in any later upgrade.
126
143
 
127
144
  ## Compatibility
128
145
 
package/bin/init.mjs CHANGED
@@ -27,9 +27,17 @@ const flags = new Set(args.filter((a) => a.startsWith("--")))
27
27
  const positional = args.filter((a) => !a.startsWith("--"))
28
28
  const targetArg = positional[0] ?? "."
29
29
  const targetDir = resolve(process.cwd(), targetArg)
30
- const projectName = targetArg === "." ? basename(targetDir) : targetArg
30
+ // `displayName` is what we show in messages and use to derive package.json
31
+ // `name`. For "." or an absolute path we just use the basename; for a
32
+ // relative path the user typed (`my-app`, `apps/sandbox`) we keep it as-is.
33
+ const isAbsolute = targetArg.startsWith("/") || /^[a-zA-Z]:\\/.test(targetArg)
34
+ const displayName = targetArg === "." || isAbsolute ? basename(targetDir) : targetArg
35
+ // `cdHint` is what we tell the user to `cd` into. If they passed an
36
+ // absolute path, that's the literal path; otherwise the relative arg.
37
+ const cdHint = isAbsolute ? targetDir : displayName
31
38
  const force = flags.has("--force")
32
39
  const skipInstall = flags.has("--no-install") || flags.has("--skip-install")
40
+ const skipExtras = flags.has("--no-extras") || flags.has("--skip-extras")
33
41
 
34
42
  if (flags.has("--help") || flags.has("-h")) {
35
43
  console.log(`
@@ -47,6 +55,9 @@ Options:
47
55
  files are NOT overwritten unless template ships the
48
56
  same path).
49
57
  --no-install Skip the dependency install step.
58
+ --no-extras Skip auto-syncing Cursor / Claude agent skills, binding
59
+ rules, and pattern docs. You can run them later with
60
+ \`exxat-ui sync-extras\`.
50
61
  -h, --help Show this message.
51
62
 
52
63
  Examples:
@@ -59,7 +70,7 @@ Examples:
59
70
 
60
71
  if (!existsSync(targetDir)) {
61
72
  mkdirSync(targetDir, { recursive: true })
62
- console.log(`📁 Created ${projectName}/`)
73
+ console.log(`📁 Created ${displayName}/`)
63
74
  }
64
75
 
65
76
  const targetStat = statSync(targetDir)
@@ -86,7 +97,7 @@ if (blockers.length > 0 && !force) {
86
97
  process.exit(1)
87
98
  }
88
99
 
89
- console.log(`📦 Copying Exxat DS starter app into ${projectName}/ …`)
100
+ console.log(`📦 Copying Exxat DS starter app into ${displayName}/ …`)
90
101
  cpSync(templateDir, targetDir, { recursive: true })
91
102
 
92
103
  // Pin DS to this CLI's published version so `latest` cache drift cannot
@@ -97,7 +108,7 @@ if (targetPkg.dependencies?.["@exxatdesignux/ui"]) {
97
108
  targetPkg.dependencies["@exxatdesignux/ui"] = `^${selfPkg.version}`
98
109
  }
99
110
  // Name the project after the target dir for nicer dev-server / process titles.
100
- targetPkg.name = projectName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase() || "my-exxat-app"
111
+ targetPkg.name = displayName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase() || "my-exxat-app"
101
112
  writeFileSync(targetPkgPath, `${JSON.stringify(targetPkg, null, 2)}\n`)
102
113
  console.log(`📌 Pinned @exxatdesignux/ui to ^${selfPkg.version} and named the project "${targetPkg.name}".`)
103
114
 
@@ -111,19 +122,41 @@ if (skipInstall) {
111
122
  } catch (err) {
112
123
  console.error("")
113
124
  console.error(`❌ ${pm.label} install failed. You can retry manually:`)
114
- console.error(` cd ${projectName} && ${pm.installCmd}`)
125
+ console.error(` cd ${cdHint} && ${pm.installCmd}`)
115
126
  process.exit(1)
116
127
  }
117
128
  }
118
129
 
130
+ // Drop the full curated bundle (Cursor + Claude skills, binding rules,
131
+ // pattern docs, handbook) into the new repo so AI assistants light up on
132
+ // first open. The bundle replaces only `exxat-*` namespaced files — the
133
+ // consumer's own rules/skills/docs are preserved on every re-run.
134
+ if (skipExtras) {
135
+ console.log(`⏭ Skipped agent skills + rules (--no-extras).`)
136
+ } else {
137
+ const syncScript = resolve(__dirname, "sync-extras.mjs")
138
+ if (!existsSync(syncScript)) {
139
+ console.warn(`⚠️ Could not find sync-extras at ${syncScript} — skipping.`)
140
+ } else {
141
+ console.log(`🧠 Installing Cursor + Claude agent skills, binding rules, and pattern docs …`)
142
+ try {
143
+ execSync(`node "${syncScript}" --quiet`, { stdio: "inherit", cwd: targetDir })
144
+ } catch (err) {
145
+ console.warn(`⚠️ Agent extras step failed. You can retry manually:`)
146
+ console.warn(` cd ${cdHint} && ${pm.dlxCmd} --package=@exxatdesignux/ui@${selfPkg.version} exxat-ui sync-extras`)
147
+ }
148
+ }
149
+ }
150
+
119
151
  console.log("")
120
- console.log(`✅ Done! Your Exxat DS app is ready in ./${projectName}/`)
152
+ console.log(`✅ Done! Your Exxat DS app is ready in ${isAbsolute ? targetDir : `./${displayName}/`}`)
121
153
  console.log("")
122
- console.log(` cd ${projectName}`)
154
+ if (targetArg !== ".") console.log(` cd ${cdHint}`)
123
155
  console.log(` ${pm.runCmd} dev # start dev server`)
124
156
  console.log("")
125
- console.log(" Optional refresh Cursor skills + pattern docs:")
126
- console.log(` ${pm.dlxCmd} --package=@exxatdesignux/ui@${selfPkg.version} exxat-ui sync-extras`)
157
+ console.log(" Cursor / Claude skills, rules, and pattern docs are already in place.")
158
+ console.log(" When you upgrade @exxatdesignux/ui later, refresh them with:")
159
+ console.log(` ${pm.dlxCmd} --package=@exxatdesignux/ui@latest exxat-ui sync-extras`)
127
160
  console.log("")
128
161
 
129
162
  /**
@@ -28,6 +28,14 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
28
28
  const pkgRoot = resolve(__dirname, "..")
29
29
  const cwd = process.cwd()
30
30
 
31
+ // `--quiet` suppresses the per-file "[sync-extras] wrote …" lines so the
32
+ // scaffolder (`create-exxat-app`) can call us inline without drowning the
33
+ // user in 50+ status lines. The final summary line still prints.
34
+ const QUIET = process.argv.includes("--quiet") || process.env.EXXAT_SYNC_EXTRAS_QUIET === "1"
35
+ function log(...m) {
36
+ if (!QUIET) console.log(...m)
37
+ }
38
+
31
39
  const SOURCES = {
32
40
  skills: join(pkgRoot, "consumer-extras", "cursor-skills"),
33
41
  rules: join(pkgRoot, "consumer-extras", "cursor-rules"),
@@ -69,7 +77,7 @@ function syncSkillsTo(label, dest) {
69
77
  if (!statSync(src).isDirectory()) continue
70
78
  if (!name.startsWith("exxat-")) continue
71
79
  replaceDir(src, join(dest, name))
72
- console.log(`[sync-extras] wrote ${label}/${name}/`)
80
+ log(`[sync-extras] wrote ${label}/${name}/`)
73
81
  wrote++
74
82
  }
75
83
  }
@@ -91,7 +99,7 @@ function syncRules() {
91
99
  if (!statSync(src).isFile()) continue
92
100
  if (!name.startsWith("exxat-") || !name.endsWith(".mdc")) continue
93
101
  cpSync(src, join(DESTINATIONS.cursorRules, name))
94
- console.log(`[sync-extras] wrote .cursor/rules/${name}`)
102
+ log(`[sync-extras] wrote .cursor/rules/${name}`)
95
103
  wrote++
96
104
  }
97
105
  }
@@ -111,7 +119,7 @@ function syncPatterns() {
111
119
  const src = join(SOURCES.patterns, name)
112
120
  if (!statSync(src).isFile()) continue
113
121
  cpSync(src, join(DESTINATIONS.patterns, name))
114
- console.log(`[sync-extras] wrote docs/exxat-ds/${name}`)
122
+ log(`[sync-extras] wrote docs/exxat-ds/${name}`)
115
123
  wrote++
116
124
  }
117
125
  }
@@ -137,7 +145,7 @@ function syncHandbook() {
137
145
  const src = join(SOURCES.handbook, name)
138
146
  if (!statSync(src).isFile()) continue
139
147
  cpSync(src, join(DESTINATIONS.handbook, name))
140
- console.log(`[sync-extras] wrote docs/exxat-ds/handbook/${name}`)
148
+ log(`[sync-extras] wrote docs/exxat-ds/handbook/${name}`)
141
149
  wrote++
142
150
  }
143
151
  }
@@ -149,4 +157,4 @@ syncPatterns()
149
157
  syncHandbook()
150
158
 
151
159
  console.log(`[sync-extras] Done. ${wrote} files / dirs written; ${skipped} optional sources missing.`)
152
- console.log("[sync-extras] Product routes and app content were not modified.")
160
+ if (!QUIET) console.log("[sync-extras] Product routes and app content were not modified.")
@@ -5687,11 +5687,12 @@ function HubTable({
5687
5687
  for (const v of supportedViewTypes) {
5688
5688
  const kind = getDataListViewRenderKind(v);
5689
5689
  if (kind === "data-table") continue;
5690
- if (renderers[kind] == null) {
5691
- console.warn(
5692
- `[Exxat DS][HubTable: ${hubLabel}] Missing renderer for supported view "${v}" (${kind}). Add a renderer entry, or remove the view from supportedViewTypes.`
5693
- );
5694
- }
5690
+ if (renderers[kind] != null) continue;
5691
+ if (kind === "list-with-toolbar" && renderListRow != null) continue;
5692
+ if (kind === "board-with-toolbar" && renderBoardCard != null && boardGroups != null) continue;
5693
+ console.warn(
5694
+ `[Exxat DS][HubTable: ${hubLabel}] Missing renderer for supported view "${v}" (${kind}). Add a renderer entry, or remove the view from supportedViewTypes.`
5695
+ );
5695
5696
  }
5696
5697
  }
5697
5698
  const composed = {