@godxjp/ui 27.5.0 → 27.6.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.
@@ -44,7 +44,10 @@ const RangeTimeline = React.forwardRef(
44
44
  "div",
45
45
  {
46
46
  className: "ui-range-timeline-canvas",
47
- style: { "--range-timeline-columns": Math.max(1, columns.length) },
47
+ style: {
48
+ "--range-timeline-columns": Math.max(1, columns.length),
49
+ "--range-timeline-units": units
50
+ },
48
51
  children: [
49
52
  bands && bands.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-header", children: [
50
53
  /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", "aria-hidden": "true" }),
@@ -52,7 +55,7 @@ const RangeTimeline = React.forwardRef(
52
55
  "div",
53
56
  {
54
57
  className: "ui-range-timeline-column",
55
- style: { flex: band.units },
58
+ style: { gridColumn: `span ${band.units}` },
56
59
  children: /* @__PURE__ */ jsx(Text, { size: "xs", weight: "medium", children: band.label })
57
60
  },
58
61
  index
@@ -64,15 +67,15 @@ const RangeTimeline = React.forwardRef(
64
67
  "div",
65
68
  {
66
69
  className: "ui-range-timeline-column",
67
- style: { flex: column.units },
70
+ style: { gridColumn: `span ${column.units}` },
68
71
  children: /* @__PURE__ */ jsx(Text, { size: "xs", children: column.label })
69
72
  },
70
73
  index
71
74
  )) })
72
75
  ] }),
73
76
  /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-body", children: [
74
- (bordered || columns.some((column) => column.muted)) && // Decorative: the same flex/units layout as the header columns, laid once behind every
75
- // row so each vertical rule runs the full body height exactly under its header column.
77
+ (bordered || columns.some((column) => column.muted)) && // Decorative: the same unit tracks as the header columns, laid once behind every row so
78
+ // each vertical rule runs the full body height exactly under its header column.
76
79
  /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-grid", "aria-hidden": "true", children: [
77
80
  /* @__PURE__ */ jsx("div", {}),
78
81
  /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-columns", children: columns.map((column, index) => /* @__PURE__ */ jsx(
@@ -80,7 +83,7 @@ const RangeTimeline = React.forwardRef(
80
83
  {
81
84
  className: "ui-range-timeline-grid-column",
82
85
  "data-muted": column.muted ? "true" : void 0,
83
- style: { flex: column.units }
86
+ style: { gridColumn: `span ${column.units}` }
84
87
  },
85
88
  index
86
89
  )) })
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "27.5.0",
3
+ "version": "27.6.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -745,8 +745,9 @@ export type CalendarProp = DayPickerProps & CalendarFooterProp & {
745
745
  *
746
746
  * Default `true` — the owner's call, from a real screen: with no ruling the month read as a
747
747
  * cloud of numbers and was "very hard to read". Pass `bordered={false}` for the floating-button
748
- * look. The line colour is `--calendar-grid-border-color` (default `--input`, the ≥3:1 tier;
749
- * the decorative `--border` measured 1.15:1 and was invisible as a 1px ruling).
748
+ * look. The line colour is `--calendar-grid-border-color` (default `--input` at 0.5 alpha,
749
+ * 1.74:1 light / 1.95:1 dark on the popover; the full `--input` read too dark, and the
750
+ * decorative `--border` measured 1.15:1 and was invisible as a 1px ruling).
750
751
  */
751
752
  bordered?: boolean;
752
753
  /** Decorate a day cell — 祝日, a booked day, a deadline. @see CalendarCellRenderProp */
@@ -2229,13 +2229,13 @@
2229
2229
 
2230
2230
  .ui-calendar[data-bordered="true"] .ui-calendar-day,
2231
2231
  .ui-calendar[data-bordered="true"] .ui-calendar-weekday {
2232
- border-inline-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input)));
2233
- border-block-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input)));
2232
+ border-inline-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input) / 0.5));
2233
+ border-block-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input) / 0.5));
2234
2234
  }
2235
2235
 
2236
2236
  .ui-calendar[data-bordered="true"] .ui-calendar-week,
2237
2237
  .ui-calendar[data-bordered="true"] .ui-calendar-weekdays {
2238
- border-inline-end: 1px solid var(--calendar-grid-border-color, hsl(var(--input)));
2238
+ border-inline-end: 1px solid var(--calendar-grid-border-color, hsl(var(--input) / 0.5));
2239
2239
  }
2240
2240
 
2241
2241
  .ui-calendar[data-bordered="true"] .ui-calendar-weekday {
@@ -2247,7 +2247,7 @@
2247
2247
  }
2248
2248
 
2249
2249
  .ui-calendar[data-bordered="true"] .ui-calendar-grid {
2250
- border-block-end: 1px solid var(--calendar-grid-border-color, hsl(var(--input)));
2250
+ border-block-end: 1px solid var(--calendar-grid-border-color, hsl(var(--input) / 0.5));
2251
2251
  }
2252
2252
 
2253
2253
  .ui-calendar[data-bordered="true"] .ui-calendar-day.invisible {
@@ -2268,8 +2268,8 @@
2268
2268
 
2269
2269
  .ui-calendar[data-bordered="true"] .ui-calendar-week-number,
2270
2270
  .ui-calendar[data-bordered="true"] .ui-calendar-week-number-header {
2271
- border-inline-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input)));
2272
- border-block-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input)));
2271
+ border-inline-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input) / 0.5));
2272
+ border-block-start: 1px solid var(--calendar-grid-border-color, hsl(var(--input) / 0.5));
2273
2273
  }
2274
2274
 
2275
2275
  .ui-calendar[data-bordered="true"] .ui-calendar-day {
@@ -1351,8 +1351,10 @@
1351
1351
  padding: var(--space-2);
1352
1352
  border-inline-end: 1px solid hsl(var(--range-timeline-border-color));
1353
1353
  }
1354
+
1354
1355
  .ui-range-timeline-columns {
1355
- display: flex;
1356
+ display: grid;
1357
+ grid-template-columns: repeat(var(--range-timeline-units, 1), minmax(0, 1fr));
1356
1358
  }
1357
1359
  .ui-range-timeline-column {
1358
1360
  min-inline-size: 0;
@@ -1390,12 +1392,12 @@
1390
1392
  .ui-range-timeline[data-bordered="true"] .ui-range-timeline-column,
1391
1393
  .ui-range-timeline[data-bordered="true"] .ui-range-timeline-label,
1392
1394
  .ui-range-timeline[data-bordered="true"] .ui-range-timeline-grid-column {
1393
- border-inline-end-color: var(--range-timeline-grid-color, hsl(var(--input)));
1395
+ border-inline-end-color: var(--range-timeline-grid-color, hsl(var(--input) / 0.5));
1394
1396
  }
1395
1397
  .ui-range-timeline[data-bordered="true"] .ui-range-timeline-header + .ui-range-timeline-header,
1396
1398
  .ui-range-timeline[data-bordered="true"] .ui-range-timeline-row {
1397
1399
  border-block-start: var(--range-timeline-grid-width) solid
1398
- var(--range-timeline-grid-color, hsl(var(--input)));
1400
+ var(--range-timeline-grid-color, hsl(var(--input) / 0.5));
1399
1401
  }
1400
1402
  .ui-range-timeline-outside {
1401
1403
  position: absolute;
@@ -200,6 +200,42 @@ export default function Demo() {
200
200
  </CardContent>
201
201
  </Card>
202
202
 
203
+ <Card>
204
+ <CardHeader>
205
+ <CardTitle level={2}>月をまたぐ日単位の軸(bands)</CardTitle>
206
+ <CardDescription>
207
+ 9月25日から10月7日まで。bands
208
+ の月の境界は、その月の1日の列の境界にぴったり重なります。
209
+ 始まりが月の途中なので、最初の月の帯は6日分だけの幅です。
210
+ </CardDescription>
211
+ </CardHeader>
212
+ <CardContent>
213
+ <RangeTimeline
214
+ label={t("rangeTimeline.schedule")}
215
+ bands={[
216
+ { label: monthLabel(8), units: 6 },
217
+ { label: monthLabel(9), units: 7 },
218
+ ]}
219
+ columns={Array.from({ length: 13 }, (_, index) => {
220
+ const date = new Date(Date.UTC(2026, 8, 25 + index));
221
+ return {
222
+ label: new Intl.NumberFormat(locale).format(date.getUTCDate()),
223
+ units: 1,
224
+ muted: date.getUTCDay() === 0 || date.getUTCDay() === 6,
225
+ };
226
+ })}
227
+ rows={[
228
+ { id: "migration", label: "移行", start: 3, end: 8 },
229
+ { id: "october", label: "10月の作業", start: 6, end: 12 },
230
+ ].map((row) => ({
231
+ ...row,
232
+ startLabel: dayLabel(25 + row.start),
233
+ endLabel: dayLabel(25 + row.end),
234
+ }))}
235
+ />
236
+ </CardContent>
237
+ </Card>
238
+
203
239
  <Card>
204
240
  <CardHeader>
205
241
  <CardTitle level={2}>月単位の軸(列ごとに units が異なる)</CardTitle>
@@ -77,9 +77,8 @@ export default function Demo() {
77
77
  罫線 (bordered) · 既定で表示 / bordered={"{false}"} で解除
78
78
  </CardTitle>
79
79
  <CardDescription>
80
- 日と日の間に罫線を引くのが既定。線色は --calendar-grid-border-color (既定
81
- --input、背景に対し 3:1
82
- 以上)。選択日・今日・範囲の始点/中間/終点・無効日・前後月の日が
80
+ 日と日の間に罫線を引くのが既定。線色は --calendar-grid-border-color (既定 --input の
81
+ 50%、背景に対し約 1.7:1)。選択日・今日・範囲の始点/中間/終点・無効日・前後月の日が
83
82
  罫線の中でも判別できる。罫線なしの浮いたボタン表示にしたい場合のみ bordered=
84
83
  {"{false}"} を渡す。
85
84
  </CardDescription>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "27.5.0",
4
- "godxUiMcp": "27.5.0",
3
+ "version": "27.6.0",
4
+ "godxUiMcp": "27.6.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -16,12 +16,14 @@ import {
16
16
  existsSync,
17
17
  mkdirSync,
18
18
  readFileSync,
19
+ realpathSync,
19
20
  renameSync,
20
21
  statSync,
21
22
  unlinkSync,
22
23
  writeFileSync,
23
24
  } from "node:fs";
24
- import { basename, dirname, join } from "node:path";
25
+ import { homedir } from "node:os";
26
+ import { basename, dirname, join, resolve } from "node:path";
25
27
  import { fileURLToPath } from "node:url";
26
28
 
27
29
  /** This package's own root — the source of the version we stamp with. */
@@ -425,6 +427,83 @@ export function ensureMcpJson(root) {
425
427
  return created ? "created" : "added";
426
428
  }
427
429
 
430
+ /**
431
+ * Registrations of the catalog server OUTSIDE the project — READ-ONLY (gh#722).
432
+ *
433
+ * `.mcp.json` is not the only place an agent's MCP servers come from. Claude Code keeps
434
+ * user-scoped servers in the top-level `mcpServers` of `~/.claude.json` (under
435
+ * `$CLAUDE_CONFIG_DIR` when that is set) and local-scoped ones — private to one checkout — under
436
+ * `projects[<absolute project path>].mcpServers` of the same file. Both are live beside the
437
+ * project's entry. Measured: a user-scoped `godxjp-ui` serving a ≤ 21.0.0 catalog answered next
438
+ * to the project's pinned `godx-ui`, and an agent took a removed component's page from it.
439
+ *
440
+ * That file belongs to the user, spans every project, and is not ours to edit: this only READS
441
+ * it. A missing, unreadable or malformed file yields nothing, silently. Returns
442
+ * `{ key, scope, pin }` per entry — never `env`, which is where secrets live.
443
+ */
444
+ export function findOutOfProjectUiMcp(
445
+ root,
446
+ configDir = process.env.CLAUDE_CONFIG_DIR || homedir(),
447
+ ) {
448
+ try {
449
+ const read = readJsonFile(join(configDir, ".claude.json"));
450
+ if (read.state !== "ok") return [];
451
+ const found = [];
452
+ const collect = (servers, scope) => {
453
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) return;
454
+ for (const [key, entry] of Object.entries(servers)) {
455
+ if (!runsUiMcp(entry)) continue;
456
+ const parts = [entry.command, ...(Array.isArray(entry.args) ? entry.args : [])];
457
+ const pin = parts.find(
458
+ (part) => typeof part === "string" && /(^|\/)@godxjp\/ui-mcp(@|$)/.test(part),
459
+ );
460
+ found.push({ key, scope, pin });
461
+ }
462
+ };
463
+ collect(read.json.mcpServers, "user");
464
+ const projects = read.json.projects;
465
+ if (projects && typeof projects === "object" && !Array.isArray(projects)) {
466
+ const paths = new Set([resolve(root)]);
467
+ try {
468
+ paths.add(realpathSync(root));
469
+ } catch {
470
+ // A root that cannot be resolved is looked up by its given path only.
471
+ }
472
+ for (const path of paths) collect(projects[path]?.mcpServers, "local");
473
+ }
474
+ return found;
475
+ } catch {
476
+ return [];
477
+ }
478
+ }
479
+
480
+ const shellWord = (word) => (/^[\w.@/-]+$/.test(word) ? word : `'${word.replace(/'/g, "'\\''")}'`);
481
+
482
+ /**
483
+ * What `sync-rules` and `init-agent` print about out-of-project registrations: key, pin, that it
484
+ * was left untouched, and the command that updates it. Empty string when there are none.
485
+ */
486
+ export function outOfProjectUiMcpReport(root, configDir) {
487
+ const found = findOutOfProjectUiMcp(root, configDir);
488
+ if (found.length === 0) return "";
489
+ const expected = mcpServerFor(root).args[0];
490
+ let out =
491
+ `\n @godxjp/ui → @godxjp/ui-mcp is ALSO registered outside this project, in Claude Code's ` +
492
+ `.claude.json.\n It answers beside the project's .mcp.json entry (${expected}) and an agent ` +
493
+ `can call either — the tool names differ only by the key.\n`;
494
+ for (const { key, scope, pin } of found) {
495
+ const k = shellWord(key);
496
+ const runs = /@godxjp\/ui-mcp@\S+$/.test(pin ?? "")
497
+ ? pin
498
+ : `${pin ?? "@godxjp/ui-mcp"} (unpinned: resolves the latest release)`;
499
+ out +=
500
+ ` • "${key}" (${scope} scope) runs ${runs} — outside the project, left untouched.\n` +
501
+ ` remove it: claude mcp remove ${k} -s ${scope}\n` +
502
+ ` or re-pin: claude mcp remove ${k} -s ${scope} && claude mcp add ${k} -s ${scope} -- npx ${expected}\n`;
503
+ }
504
+ return out;
505
+ }
506
+
428
507
  /** Ensure `.claude/settings.json` has the audit PostToolUse + workflow SessionStart hooks. */
429
508
  export function ensureClaudeHooks(root) {
430
509
  const path = join(root, ".claude", "settings.json");
package/scripts/cli.mjs CHANGED
@@ -34,7 +34,11 @@ const HELP = {
34
34
  Refresh the package-owned agent files for the installed @godxjp/ui version: the godx-ui entry in
35
35
  .mcp.json, the managed CLAUDE.md block, .claude/godxjp-ui-workflow.md and .ai/rules/godxjp-ui.md.
36
36
  It is the postinstall step, run by hand — use it when the app installs with ignore-scripts=true,
37
- where postinstall never runs and the rules go stale. Silent no-op in CI and when opted out.`,
37
+ where postinstall never runs and the rules go stale. Silent no-op in CI and when opted out.
38
+
39
+ It also READS Claude Code's ~/.claude.json and reports any @godxjp/ui-mcp registered there (user
40
+ scope, or local scope for this project): key, pin, and the claude mcp command that updates it.
41
+ That file is outside the project and is never written; env values are never printed.`,
38
42
  audit: `godxjp-ui audit [dir …] [--changed] [--format json] [--quiet] [--rules]
39
43
 
40
44
  Static UI-standardization audit over source (regex, no browser, fast). Exits non-zero on errors.
@@ -4,6 +4,7 @@ import {
4
4
  ensureClaudeHooks,
5
5
  ensureClaudeMd,
6
6
  ensureMcpJson,
7
+ outOfProjectUiMcpReport,
7
8
  shouldSkip,
8
9
  writeWorkflowMd,
9
10
  } from "./_agent-setup.mjs";
@@ -28,6 +29,8 @@ console.log(
28
29
  );
29
30
  console.log(` • .claude/godxjp-ui-workflow.md — ${md ? "created" : "already present"}`);
30
31
  console.log(` • CLAUDE.md — godxjp-ui mandate (${claudeMd})`);
32
+ const outside = outOfProjectUiMcpReport(root);
33
+ if (outside) console.log(outside);
31
34
  console.log(`
32
35
  The auto-audit hook now runs on every Write/Edit of a .tsx file and feeds findings
33
36
  back to the agent — it cannot skip the audit. Restart your agent to load the MCP + hooks.
@@ -8,6 +8,7 @@ import {
8
8
  ensureClaudeMd,
9
9
  ensureMcpJson,
10
10
  ensureConsumerRules,
11
+ outOfProjectUiMcpReport,
11
12
  refreshGuineaPigSkill,
12
13
  shouldSkip,
13
14
  writeWorkflowMd,
@@ -25,6 +26,10 @@ try {
25
26
  if (r.startsWith("left untouched") || r.startsWith("present (custom godx-ui")) {
26
27
  console.log(`\n @godxjp/ui → .mcp.json ${r}\n`);
27
28
  }
29
+ // A registration outside the project is reported whatever the project file says, before any of
30
+ // the quiet exits below: it is live beside ours and nothing here will ever change it (gh#722).
31
+ const outside = outOfProjectUiMcpReport(root);
32
+ if (outside) console.log(outside);
28
33
  // The mandate is plain text the agent reads every turn (CLAUDE.md block + workflow file). It
29
34
  // changes nothing in the dev loop, so it is installed by default: an agent that never saw the
30
35
  // but no mandate). Only the hooks — which DO change the loop — stay behind `init-agent`.