@godxjp/ui 31.0.2 → 31.0.3

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.
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 31.0.2.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.0.3.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
package/agent/index.json CHANGED
@@ -48,7 +48,7 @@
48
48
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.0.2/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.0.3/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -62,5 +62,5 @@
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "31.0.2"
65
+ "version": "31.0.3"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
3
  > A Japanese-enterprise React design system: 175 components, 2074 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.0.2.
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.0.3.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@31.0.2`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.0.3`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v31.0.2/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.0.3/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -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": "31.0.2",
3
+ "version": "31.0.3",
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,
@@ -28,6 +28,8 @@ export declare function translate(locale: AppLocale, fallbackLocale: AppLocale,
28
28
  /** Non-React translate using module-synced locale (see `syncI18nLocale`). */
29
29
  export declare function translateCurrent(key: MessageKey, params?: TranslateParams): string;
30
30
  export declare function syncI18nLocale(locale: AppLocale, fallbackLocale: AppLocale): void;
31
+ /** Whether anything has ever called `syncI18nLocale` — the resting "vi" was never CHOSEN. */
32
+ export declare function isI18nLocaleSynced(): boolean;
31
33
  export declare function getSyncedLocale(): AppLocale;
32
34
  export declare function getSyncedFallbackLocale(): AppLocale;
33
35
  /** Reset for tests. */
@@ -81,9 +81,14 @@ function translateCurrent(key, params) {
81
81
  }
82
82
  let syncedLocale = "vi";
83
83
  let syncedFallbackLocale = "en";
84
+ let syncedByCaller = false;
84
85
  function syncI18nLocale(locale, fallbackLocale) {
85
86
  syncedLocale = locale;
86
87
  syncedFallbackLocale = fallbackLocale;
88
+ syncedByCaller = true;
89
+ }
90
+ function isI18nLocaleSynced() {
91
+ return syncedByCaller;
87
92
  }
88
93
  function getSyncedLocale() {
89
94
  return syncedLocale;
@@ -94,11 +99,13 @@ function getSyncedFallbackLocale() {
94
99
  function resetI18nLocale() {
95
100
  syncedLocale = "vi";
96
101
  syncedFallbackLocale = "en";
102
+ syncedByCaller = false;
97
103
  }
98
104
  export {
99
105
  MESSAGE_CATALOG,
100
106
  getSyncedFallbackLocale,
101
107
  getSyncedLocale,
108
+ isI18nLocaleSynced,
102
109
  registerMessages,
103
110
  resetI18nLocale,
104
111
  syncI18nLocale,
@@ -1,15 +1,16 @@
1
1
  import type { DayPickerProps } from "react-day-picker";
2
- import type { AppLocale } from "../app/types.js";
3
2
  import { type MessageKey, type TranslateParams } from "./translate.js";
3
+ /** Test seam: the once-only warning is module state. */
4
+ export declare function resetOutsideProviderWarningForTests(): void;
4
5
  type DayPickerLocale = NonNullable<DayPickerProps["locale"]>;
5
6
  export declare function useTranslation(): {
6
- locale: AppLocale;
7
- fallbackLocale: AppLocale;
7
+ locale: import("../app/index.js").AppLocale;
8
+ fallbackLocale: import("../app/index.js").AppLocale;
8
9
  t: (key: MessageKey, params?: TranslateParams) => string;
9
10
  };
10
11
  /** date-fns + react-day-picker locales + datetime prefs from AppProvider. */
11
12
  export declare function usePickerLocales(dayPickerOverride?: DayPickerLocale): {
12
- locale: AppLocale;
13
+ locale: import("../app/index.js").AppLocale;
13
14
  timezone: string;
14
15
  timeFormat: import("../app/index.js").AppTimeFormat;
15
16
  dateFormat: import("../app/index.js").AppDateFormat;
@@ -2,13 +2,30 @@
2
2
  import { useMemo } from "react";
3
3
  import { useOptionalAppContext } from "../app/app-provider.js";
4
4
  import { getDateFnsLocale, getDayPickerLocale } from "../app/locales.js";
5
- import { translate } from "./translate.js";
6
- const DEFAULT_LOCALE = "vi";
7
- const DEFAULT_FALLBACK = "en";
5
+ import { resolveHydrationSafeTimezone } from "../app/timezones.js";
6
+ import { isDevelopment } from "../lib/dev.js";
7
+ import {
8
+ getSyncedFallbackLocale,
9
+ getSyncedLocale,
10
+ isI18nLocaleSynced,
11
+ translate
12
+ } from "./translate.js";
13
+ let warnedOutsideProvider = false;
14
+ function warnOutsideProvider(locale) {
15
+ if (warnedOutsideProvider || !isDevelopment() || isI18nLocaleSynced()) return;
16
+ warnedOutsideProvider = true;
17
+ console.warn(
18
+ `[@godxjp/ui] A kit component rendered outside <AppProvider>, so its built-in strings (close/cancel/delete labels, counters, pickers) use the resting locale "${locale}" \u2014 nothing chose it. Wrap the app in <AppProvider defaultLocale="\u2026"> (docs/CONSUMER-RULES.md), or call syncI18nLocale(locale, fallback) at startup if you truly cannot. Shown once.`
19
+ );
20
+ }
21
+ function resetOutsideProviderWarningForTests() {
22
+ warnedOutsideProvider = false;
23
+ }
8
24
  function useTranslation() {
9
25
  const ctx = useOptionalAppContext();
10
- const locale = ctx?.locale ?? DEFAULT_LOCALE;
11
- const fallbackLocale = ctx?.fallbackLocale ?? DEFAULT_FALLBACK;
26
+ const locale = ctx?.locale ?? getSyncedLocale();
27
+ const fallbackLocale = ctx?.fallbackLocale ?? getSyncedFallbackLocale();
28
+ if (!ctx) warnOutsideProvider(locale);
12
29
  return useMemo(
13
30
  () => ({
14
31
  locale,
@@ -20,11 +37,14 @@ function useTranslation() {
20
37
  }
21
38
  function usePickerLocales(dayPickerOverride) {
22
39
  const ctx = useOptionalAppContext();
23
- const locale = ctx?.locale ?? DEFAULT_LOCALE;
40
+ const locale = ctx?.locale ?? getSyncedLocale();
41
+ if (!ctx) warnOutsideProvider(locale);
24
42
  return useMemo(
25
43
  () => ({
26
44
  locale,
27
- timezone: ctx?.timezone ?? "Asia/Ho_Chi_Minh",
45
+ // AppProvider's own unconfigured answer (gh#968) — a zone that looks right when it is wrong
46
+ // (Asia/Ho_Chi_Minh) is worse than one that is visibly not local.
47
+ timezone: ctx?.timezone ?? resolveHydrationSafeTimezone("browser"),
28
48
  timeFormat: ctx?.timeFormat ?? "24h",
29
49
  dateFormat: ctx?.dateFormat ?? "dmy",
30
50
  dateFnsLocale: ctx?.dateFnsLocale ?? getDateFnsLocale(locale),
@@ -42,6 +62,7 @@ function usePickerLocales(dayPickerOverride) {
42
62
  );
43
63
  }
44
64
  export {
65
+ resetOutsideProviderWarningForTests,
45
66
  usePickerLocales,
46
67
  useTranslation
47
68
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.0.2",
2
+ "version": "31.0.3",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -3,7 +3,7 @@
3
3
  Read this once; the audit enforces it. Everything else in `docs/` is for contributors.
4
4
 
5
5
  1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled, 729 sliced woff2 faces, ~11.7 MB), `@import "@godxjp/ui/styles/core"` (no `@font-face` at all), `@import "@godxjp/ui/styles/core-with-fallbacks"` (`core` + six `local()`-only metric-matched faces, still zero network bytes — for when you supply Noto Sans JP yourself) or `@import "@godxjp/ui/styles/core-with-jis-level1"` (`core-with-fallbacks` + Noto Sans JP merged to JIS X 0208 level 1: **3 requests, ~1.53 MB, once** instead of ~8 font requests on every navigation — for a Japanese app that wants the bundled face). Never cherry-pick `*-layout.css`.
6
- 2. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
6
+ 2. **Mount `<AppProvider defaultLocale="…">` once, at the root.** Without it the kit's OWN strings (dialog close/cancel/delete, counters, picker labels) fall to the resting locale `vi` and pickers to UTC — measured in a Japanese app as Vietnamese buttons, silently (gh#1005). A development build now warns once when a kit component renders outside it; `syncI18nLocale(locale, fallback)` is the escape for an app that truly cannot mount the provider. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
7
7
  3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
8
8
  4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`. A LIST of those rows is `<Flex as="ul" marker="none" direction="col" gap="none">` with `<ListRow as="li">` children — `marker="none"` keeps the `<ul>`, the `<li>` semantics and the gap token while dropping the bullet and the indent. Never a raw `<ul>`/`<ol>` (it carries no gap token), never `<div role="list">` + `<div role="listitem">`, and never a wrapper around each row: the divider is `:not(:last-child)` among siblings, so a row alone in its own wrapper is always the last one and EVERY divider disappears silently. A bulleted prose list is the same `<Flex as="ul">` without `marker`.
9
9
  5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below. **Fields live in a `<Form layout="horizontal" labelWidth controlWidth>`** — never a group of `FormField`s without one, never a hand-rolled `<Flex>` row of fields (a row that belongs together is `<SpaceCompact>` or `<Form columns>`); size each control for its content with `controlWidth` (GOV.UK text-input width: a port ~7rem, a short enum ~10rem); three or more fields is a page, not a Dialog. The audit rules are `formfield-needs-form`, `dialog-form-too-big` and `select-width-hint`. **A case the kit cannot express is an issue, not a hand-roll:** open it on godx-jp/godxjp-ui, use the nearest valid composition, and mark it `// TODO(godxjp-ui#<n>)` (gh#998).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.0.2",
4
- "godxUiMcp": "31.0.2",
3
+ "version": "31.0.3",
4
+ "godxUiMcp": "31.0.3",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -120,6 +120,14 @@ ngày: năm thứ cần đều ĐÃ CÓ và vẫn bị dựng lại bằng thứ
120
120
  Lỗi không phải "đoán sai tên prop" mà là **cho rằng nó không tồn tại nên không
121
121
  hỏi**.
122
122
 
123
+ ## `AppProvider` là bắt buộc — thiếu nó, chuỗi của kit nói tiếng Việt
124
+
125
+ Mount `<AppProvider defaultLocale="ja">` MỘT lần ở gốc app. Thiếu nó, chuỗi của chính kit (nút
126
+ đóng/hủy/xóa của dialog, upload, badge đếm, nhãn picker) rơi về locale nghỉ `vi` và picker rơi về
127
+ UTC — godx-mailer đo được nút tiếng Việt giữa UI tiếng Nhật, không một cảnh báo (gh#1005). Bản dev
128
+ giờ cảnh báo MỘT lần khi component của kit render ngoài provider. Không mount được provider thì gọi
129
+ `syncI18nLocale(locale, fallback)` lúc khởi động.
130
+
123
131
  ## Form: `Form` bọc `FormField`, bề rộng theo nội dung, form dài là PAGE
124
132
 
125
133
  godx-mailer dính bốn lỗi form trong một ngày, cả bốn đều là kit ĐÃ CÓ đồ đúng