melta-app 0.5.2 → 0.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.
- package/README.md +288 -152
- package/docs/architecture.md +92 -0
- package/eslint-rules/melta.mjs +26 -0
- package/lib/module/primitives/Text.js +18 -5
- package/lib/module/primitives/Text.js.map +1 -1
- package/lib/module/primitives/metric.styles.js +4 -1
- package/lib/module/primitives/metric.styles.js.map +1 -1
- package/lib/module/primitives/text.styles.js +7 -1
- package/lib/module/primitives/text.styles.js.map +1 -1
- package/lib/module/theme/define-theme.js +13 -0
- package/lib/module/theme/define-theme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/line-height.js +43 -0
- package/lib/module/theme/line-height.js.map +1 -0
- package/lib/module/theme/native-theme.js +7 -6
- package/lib/module/theme/native-theme.js.map +1 -1
- package/lib/typescript/src/primitives/Text.d.ts.map +1 -1
- package/lib/typescript/src/primitives/metric.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.styles.d.ts.map +1 -1
- package/lib/typescript/src/theme/define-theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/index.d.ts +1 -0
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/line-height.d.ts +34 -0
- package/lib/typescript/src/theme/line-height.d.ts.map +1 -0
- package/lib/typescript/src/theme/native-theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +9 -0
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/src/primitives/Text.tsx +19 -3
- package/src/primitives/metric.styles.ts +8 -1
- package/src/primitives/text.styles.ts +10 -1
- package/src/theme/define-theme.ts +18 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/line-height.ts +44 -0
- package/src/theme/native-theme.ts +7 -6
- package/src/theme/types.ts +9 -0
package/README.md
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
# melta for APP(melta-app)
|
|
2
2
|
|
|
3
|
-
**React Native
|
|
3
|
+
**web と同じデザイン契約を React Native に降ろす UI kit。使う側のコードにも lint 強制層を npm で配る。**
|
|
4
|
+
アプリ本体ではなく `npm install melta-app` で使うライブラリ。
|
|
5
|
+
|
|
6
|
+
- **契約は共有** — web 版 [melta-ui](https://github.com/tsubotax/melta-ui) と同じ `melta-contracts`(JSON)が tokens / 禁止ルール / component 契約の single source of truth
|
|
7
|
+
- **RN コンポーネント** — その契約を満たす React Native(Expo)実装。[対応表](#コンポーネント)は契約から生成する
|
|
8
|
+
- **利用側コードの lint 強制層** — [`melta-app/eslint-plugin`](#利用側コードの-lint-強制層melta-appeslint-plugin) を同梱。DS を**使う側**のプロジェクトで生値の直書きが lint で止まる
|
|
4
9
|
|
|
5
10
|
> 1 つのデザイン言語が、web では Tailwind に、app では React Native に降りる。
|
|
6
11
|
|
|
7
12
|
- **Showcase / Live Catalog**: https://app.melta.tsubotax.com (Live Catalog は実 RN コンポーネントの web export。HTML 再現デモではない)
|
|
8
13
|
- **Web 版 showcase**: https://melta.tsubotax.com
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
## Proof(機械で担保している範囲)
|
|
16
|
+
|
|
17
|
+
このリポジトリで検証できる主張には実行コマンドを付けてある。証拠として挙げたリポジトリ内の
|
|
18
|
+
パスとアンカーは drift 検査(`npm run check:drift`)が実在を照合するので、リンクが腐れば CI が落ちる。
|
|
19
|
+
※実導入検証(下記 2 項目め)だけは非公開プロジェクトでの**実測報告**で、読者が再実行できるのは
|
|
20
|
+
同じ欠陥を固定した E2E テストまで。
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
- **ドキュメントは契約からの生成物** — 下の[コンポーネント表](#コンポーネント)・[llms.txt](https://app.melta.tsubotax.com/llms.txt)・showcase の統計は `melta-contracts` から生成し、手書きの表はこのリポに存在しない。腐りは drift 検査が落とす([scripts/check-drift.ts](./scripts/check-drift.ts) / [CI](./.github/workflows/check.yml))
|
|
23
|
+
- **消費者プロジェクトでの実導入検証(2026-08-04)** — 別リポジトリの自プロジェクト(非公開 RN アプリ)に npm 経由で導入し、AI が違反コードを書いた直後に検出 → 修正フィードバック → 自己修正、のループを実測で確認。導入時に見つかった hook の欠陥(実行失敗時に無言で素通りする)は同日中に本体へ還元し、故障系を含む E2E 14 ケースで固定([scripts/lib/hook-lint.test.ts](./scripts/lib/hook-lint.test.ts) / [CHANGELOG 0.5.3](./CHANGELOG.md#053--2026-08-04))
|
|
24
|
+
- **「npm install すれば動く」の機械証明** — pack → tarball 実体検査 → fixture へ install → 本体 / icons / safe-area の import + typecheck → lint plugin を実 import してルール 4 本と推奨 severity を照合、までを公開ゲートにしている([scripts/check-installability.sh](./scripts/check-installability.sh)。`npm run release` の必須ステップ)
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
15
27
|
|
|
16
28
|
```bash
|
|
17
29
|
npm install melta-app
|
|
18
30
|
```
|
|
19
31
|
|
|
20
|
-
peerDependencies は `react` / `react-native`
|
|
32
|
+
必須の peerDependencies は `react` / `react-native` の 2 つ(runtime 依存ゼロ)。機能別の optional peer が 2 つ — `react-native-svg`(`melta-app/icons` を使う場合のみ)と `react-native-safe-area-context`(`melta-app/safe-area` を使う場合のみ)。検証済み構成は Expo 56 / RN 0.85 / React 19.2。
|
|
33
|
+
|
|
34
|
+
> ⚠️ **0.x 系につき破壊的変更は minor で入る**(CHANGELOG 方針)。`^` 範囲でなく**バージョン固定か `~` 範囲**を推奨。
|
|
21
35
|
|
|
22
36
|
```tsx
|
|
23
37
|
import { ThemeProvider, Screen, Header, Card, Text, Button } from "melta-app";
|
|
@@ -36,142 +50,124 @@ export default function App() {
|
|
|
36
50
|
}
|
|
37
51
|
```
|
|
38
52
|
|
|
39
|
-
テーマは `ThemeProvider` が OS の light / dark に自動追従(`forcedMode` で固定も可)。トークンは `useTheme()` / `nativeTheme`
|
|
53
|
+
テーマは `ThemeProvider` が OS の light / dark に自動追従(`forcedMode` で固定も可)。トークンは `useTheme()` / `nativeTheme` から取れる。自分のブランドで塗り替えるなら[テーマを注入する](#テーマを注入するブランドトークン)。本体エントリ以外は subpath 3 つ([Icon](#iconmelta-appicons) / [SafeArea 差替](#safearea-の差し替えmelta-appsafe-area) / [lint plugin](#利用側コードの-lint-強制層melta-appeslint-plugin))。
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
## 利用側コードの lint 強制層(`melta-app/eslint-plugin`)
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
melta 契約からの逸脱を機械検知する eslint カスタムルール 4 本を同梱している。
|
|
58
|
+
消費者プロジェクトの flat config に組み込むと、生値の直書きが**使う側のコードで**止まる。
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
import { defineTheme, ThemeProvider } from "melta-app";
|
|
60
|
+
### 動く完全例
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
id: "acme",
|
|
51
|
-
color: { /* primary / body / semantic / status */ },
|
|
52
|
-
typography: { /* … */ },
|
|
53
|
-
spacing: { /* … */ }, radius: { /* … */ },
|
|
54
|
-
elevation: { /* … */ }, motion: { /* … */ }, zIndex: { /* … */ },
|
|
55
|
-
});
|
|
62
|
+
melta の config は parser を持たない(**単独では TS/TSX が lint されない** — 後述)。
|
|
63
|
+
RN の base config と組み合わせた、コピペで動く最小構成が以下:
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
65
|
+
```bash
|
|
66
|
+
npm i -D eslint @react-native/eslint-config prettier
|
|
60
67
|
```
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
id: "acme",
|
|
67
|
-
color: { /* … */ semantic: { dark: darkColors } }, // light は書かない
|
|
68
|
-
/* … */
|
|
69
|
-
});
|
|
70
|
-
```
|
|
69
|
+
```js
|
|
70
|
+
// eslint.config.mjs
|
|
71
|
+
import rnConfig from "@react-native/eslint-config/flat";
|
|
72
|
+
import { meltaPlugin } from "melta-app/eslint-plugin";
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- `theme.color.semantic.light` を直接読むと、原因を名指しするエラーで落ちる(値を捏造して静かに間違った色を返すことはしない)。現在 mode の色は `useTheme().colors` から取る
|
|
74
|
+
export default [
|
|
75
|
+
// theme 定義ファイルはブランドの生値そのものなので lint 対象から外す
|
|
76
|
+
{ ignores: ["**/theme.ts"] },
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
// 1. RN 標準の base config。parser(TS/TSX)と React / RN ルールはこちらが持つ
|
|
79
|
+
...rnConfig,
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
// 2. melta の推奨 severity を追加する(この 1 行)。適用範囲だけ自分で決める
|
|
82
|
+
{ ...meltaPlugin.configs.recommended, files: ["**/*.{ts,tsx}"] },
|
|
83
|
+
];
|
|
84
|
+
```
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
キーボードとスクリーンリーダーからの到達手段は**カード内の操作要素**が担う。
|
|
83
|
-
そのため `primaryAction` が必須になっている。
|
|
86
|
+
**成功判定**: 生値を書いたファイルで `npx eslint .` を実行すると、4 ルールが名前付きで出る。
|
|
84
87
|
|
|
85
|
-
```tsx
|
|
86
|
-
<Card
|
|
87
|
-
variant="action"
|
|
88
|
-
onPress={openLog}
|
|
89
|
-
primaryAction={<Button label="製作ログを見る" onPress={openLog} />}
|
|
90
|
-
>
|
|
91
|
-
<Text variant="lg" role="heading">工房 #12</Text>
|
|
92
|
-
</Card>
|
|
93
88
|
```
|
|
89
|
+
src/Bad.tsx
|
|
90
|
+
14:22 error 生の色 "#ff0000" は禁止。theme.color.* を使う。 melta/no-raw-color
|
|
91
|
+
15:5 error borderRadius の生数値は禁止。theme.radius.* を使う。 melta/no-raw-radius
|
|
92
|
+
16:5 warning spacing の生数値は theme.spacing.* 推奨。 melta/no-raw-spacing
|
|
93
|
+
19:5 warning fontSize の生数値は theme.typography.fontSize.* 推奨。 melta/no-raw-fontsize
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
- カード内に操作要素が複数あってよい。`primaryAction` に置くのは**主アクション**(面を押したときの遷移先と一致するもの)
|
|
98
|
-
- 面に `accessibilityRole="button"` は付かない。`Button` を内包しても DOM が壊れない
|
|
99
|
-
|
|
100
|
-
0.4.x からの移行は [CHANGELOG](./CHANGELOG.md#050--2026-07-29) を参照。
|
|
95
|
+
✖ 4 problems (2 errors, 2 warnings)
|
|
96
|
+
```
|
|
101
97
|
|
|
102
|
-
|
|
98
|
+
`theme.*` 経由で書き直すと 0 件になる。ここまでが「利用側に強制層が入った」状態。
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
> この例は eslint 9.39 / `@react-native/eslint-config` 0.85.3 / melta-app 0.5.3 の
|
|
101
|
+
> 使い捨て fixture(tarball install)で実行して確認したもの。ルール 4 本の実在と severity は
|
|
102
|
+
> `check:installability` が毎リリース照合する。
|
|
107
103
|
|
|
108
|
-
|
|
104
|
+
### `configs.recommended` の中身
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
`configs.recommended`(flat config 形式)は plugin の登録とルール 4 本の severity を含む。
|
|
107
|
+
**severity の正本は plugin 側**なので、消費者がドキュメントから写して持つ必要はない。
|
|
111
108
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
109
|
+
| ルール | 既定 severity | 内容 |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `melta/no-raw-color` | error | 生 hex/rgb/hsl → `theme.color.*` |
|
|
112
|
+
| `melta/no-raw-radius` | error | `borderRadius` の数値直書き → `theme.radius.*` |
|
|
113
|
+
| `melta/no-raw-spacing` | warn | `padding`/`margin`/`gap` の数値直書き(false positive あり) |
|
|
114
|
+
| `melta/no-raw-fontsize` | warn | `fontSize` の数値直書き |
|
|
116
115
|
|
|
117
|
-
|
|
116
|
+
⚠️ **base config は省略できない。** `meltaPlugin.configs.recommended` は parser も `files` も
|
|
117
|
+
持たない(適用範囲を消費者側に委ねる設計)。実測される挙動は 2 つ:
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
theme/
|
|
123
|
-
types.ts NativeTheme の型
|
|
124
|
-
native-theme.ts ⚠️ 自動生成(melta-contracts → RN 正規化済み)
|
|
125
|
-
index.ts theme エントリ(ThemeProvider / useTheme / nativeTheme)
|
|
126
|
-
primitives/ Text / Button / Tag / Metric / Stack / Row
|
|
127
|
-
components/ Card / Image / Surface / Skeleton / EmptyState / Screen / Header /
|
|
128
|
-
Avatar / TextField / Toggle / Checkbox / Radio / Alert / Toast /
|
|
129
|
-
Progress / Modal / ActionSheet / BottomSheet
|
|
130
|
-
icons/ Icon + glyphs(subpath エントリ melta-app/icons、react-native-svg 隔離)
|
|
131
|
-
contracts/
|
|
132
|
-
contract-types.ts ⚠️ 自動生成(契約メタ CONTRACTS + 型)
|
|
133
|
-
index.ts 公開エントリ
|
|
134
|
-
example/ カタログアプリ(Expo、publish 除外)
|
|
135
|
-
App.tsx / catalog/ / screens/
|
|
136
|
-
scripts/
|
|
137
|
-
generate-native-theme.ts melta-contracts/tokens.json → src/theme/native-theme.ts
|
|
138
|
-
generate-contract-types.ts 契約 JSON → src/contracts/contract-types.ts
|
|
139
|
-
check-drift.ts README / catalog / allowlist の drift 検査(--write で heal)
|
|
140
|
-
check-installability.sh pack → install → import → typecheck の公開ゲート
|
|
141
|
-
lib/ token 正規化 + conformance / consumer テスト
|
|
142
|
-
```
|
|
119
|
+
- **recommended だけを書いた場合** — `.ts` / `.tsx` はそもそも lint 対象に入らず、
|
|
120
|
+
`File ignored because no matching configuration was supplied` で**黙って素通りする**(eslint 9 / 10 で確認)
|
|
121
|
+
- **`files` だけ足して parser を足さない場合** — `Parsing error: Unexpected token` で落ちる
|
|
143
122
|
|
|
144
|
-
|
|
123
|
+
どちらも「強制層が入ったつもりで入っていない」状態なので、上の完全例のように
|
|
124
|
+
parser を持つ base config(`@react-native/eslint-config/flat` や typescript-eslint 等)の**上に追加**すること。
|
|
145
125
|
|
|
146
|
-
|
|
126
|
+
### カスタマイズ
|
|
147
127
|
|
|
148
|
-
|
|
149
|
-
|---|---|---|
|
|
150
|
-
| color | hex / rgba | そのまま(semantic は light/dark mode 切替) |
|
|
151
|
-
| spacing / radius / fontSize | rem + px | px の数値を採用(RN は unitless) |
|
|
152
|
-
| shadow | CSS box-shadow | iOS shadow* + Android elevation に分解(1 ViewStyle に同居) |
|
|
153
|
-
| fontFamily | 配列 | 単一文字列(先頭採用、未ロード時 OS デフォルト fallback) |
|
|
154
|
-
| lineHeight | 比率("2.0") | `fontSize × ratio` を px 算出 |
|
|
155
|
-
| letterSpacing | em | **em ratio の数値で保持**(fontSize 相対のため token 単体で px 化不可。適用側が `fontSize × ratio` で解決) |
|
|
156
|
-
| motion duration | "150ms" | 150(数値) |
|
|
157
|
-
| motion easing | cubic-bezier | `[a,b,c,d]` tuple(`Easing.bezier(...)` に展開) |
|
|
128
|
+
severity を変える・適用範囲を絞る場合は spread して上書きする:
|
|
158
129
|
|
|
159
|
-
|
|
130
|
+
```js
|
|
131
|
+
// eslint.config.mjs(カスタマイズする場合)
|
|
132
|
+
import { meltaPlugin } from "melta-app/eslint-plugin";
|
|
160
133
|
|
|
161
|
-
|
|
162
|
-
|
|
134
|
+
export default [
|
|
135
|
+
{
|
|
136
|
+
...meltaPlugin.configs.recommended,
|
|
137
|
+
files: ["src/**/*.{ts,tsx}"],
|
|
138
|
+
rules: {
|
|
139
|
+
...meltaPlugin.configs.recommended.rules,
|
|
140
|
+
"melta/no-raw-spacing": "off", // 例: false positive が多い環境では落とす
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
```
|
|
163
145
|
|
|
164
|
-
|
|
165
|
-
# 未 install 時のみ隣の melta-ui を fallback で読む開発モード)
|
|
166
|
-
npm run generate
|
|
146
|
+
plugin の登録とルール指定を最初から自分で書く形(従来の書き方)も動く:
|
|
167
147
|
|
|
168
|
-
|
|
169
|
-
|
|
148
|
+
```js
|
|
149
|
+
// eslint.config.mjs(すべて手書きする場合)
|
|
150
|
+
import { meltaPlugin } from "melta-app/eslint-plugin";
|
|
170
151
|
|
|
171
|
-
|
|
172
|
-
|
|
152
|
+
export default [
|
|
153
|
+
{
|
|
154
|
+
plugins: { melta: meltaPlugin },
|
|
155
|
+
rules: {
|
|
156
|
+
"melta/no-raw-color": "error", // 生 hex/rgb/hsl → theme.color.*
|
|
157
|
+
"melta/no-raw-radius": "error", // borderRadius 数値直書き → theme.radius.*
|
|
158
|
+
"melta/no-raw-spacing": "warn", // padding/margin/gap 数値直書き(false positive あり)
|
|
159
|
+
"melta/no-raw-fontsize": "warn", // fontSize 数値直書き
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
];
|
|
173
163
|
```
|
|
174
164
|
|
|
165
|
+
- 依存ゼロの自己完結 ESM(eslint 本体以外に何も要らない)。ESLint 9+ の flat config を想定(このリポでは 10.x で検証)
|
|
166
|
+
- `meltaPlugin` は **named export のみ**(default export なし)
|
|
167
|
+
- eslintrc(`.eslintrc.*`)からは登録できない(ESM の named export のみのため)。flat config への移行が要る
|
|
168
|
+
- 検知は AST の構文形状ベースの補助線。変数経由・spread は漏れるので、値の純度の本丸は
|
|
169
|
+
token 経由(`theme.*`)で書く習慣の側にある([制約](#制約と正直な範囲))
|
|
170
|
+
|
|
175
171
|
## コンポーネント
|
|
176
172
|
|
|
177
173
|
契約(melta-contracts)とコンポーネントの対応。この表は手書きせず契約から生成する(`npm run check:drift` が同期を検査)。
|
|
@@ -221,10 +217,152 @@ npm run ios # または android / web
|
|
|
221
217
|
| tooltip | — | 🚫 not-planned | — | hover 前提のため。iPhone HIG に tooltip 概念なし。必要になれば長押し Hint として別契約を切る |
|
|
222
218
|
<!-- END GENERATED: component-status -->
|
|
223
219
|
|
|
224
|
-
###
|
|
220
|
+
### 押せるカード(`variant="action"` / `"link"`)
|
|
221
|
+
|
|
222
|
+
**カード面そのものは操作要素ではない。** 面を押せるのはポインタ利用者向けの近道で、
|
|
223
|
+
キーボードとスクリーンリーダーからの到達手段は**カード内の操作要素**が担う。
|
|
224
|
+
そのため `primaryAction` が必須になっている。
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
```tsx
|
|
227
|
+
<Card
|
|
228
|
+
variant="action"
|
|
229
|
+
onPress={openLog}
|
|
230
|
+
primaryAction={<Button label="製作ログを見る" onPress={openLog} />}
|
|
231
|
+
>
|
|
232
|
+
<Text variant="lg" role="heading">工房 #12</Text>
|
|
233
|
+
</Card>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- `primaryAction` は面の `onPress` と**同じ操作**を指す(別の遷移先にしない)
|
|
237
|
+
- ラベルは `primaryAction` 側に付ける。カードは名前を持たない(`role="article"` の領域)
|
|
238
|
+
- カード内に操作要素が複数あってよい。`primaryAction` に置くのは**主アクション**(面を押したときの遷移先と一致するもの)
|
|
239
|
+
- 面に `accessibilityRole="button"` は付かない。`Button` を内包しても DOM が壊れない
|
|
240
|
+
|
|
241
|
+
0.4.x からの移行は [CHANGELOG](./CHANGELOG.md#050--2026-07-29) を参照。
|
|
242
|
+
|
|
243
|
+
## テーマを注入する(ブランドトークン)
|
|
244
|
+
|
|
245
|
+
`theme` を渡すと自分のブランドトークンで塗り替わる。未指定なら melta 既定(`nativeTheme`)。
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import { defineTheme, ThemeProvider } from "melta-app";
|
|
249
|
+
|
|
250
|
+
// module スコープで1回だけ組み立てる(render 中に作ると毎レンダー参照が変わり全体が再レンダーする)
|
|
251
|
+
const theme = defineTheme({
|
|
252
|
+
id: "acme",
|
|
253
|
+
color: { /* primary / body / semantic / status */ },
|
|
254
|
+
typography: { /* … */ },
|
|
255
|
+
spacing: { /* … */ }, radius: { /* … */ },
|
|
256
|
+
elevation: { /* … */ }, motion: { /* … */ }, zIndex: { /* … */ },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export default function App() {
|
|
260
|
+
return <ThemeProvider theme={theme}>{/* … */}</ThemeProvider>;
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
省略した欄は補完されない(`defineTheme` は dev で欠けたキーを名指しして throw する)。
|
|
265
|
+
下は**全欄を埋めたコピペ可能な最小完全例** — そのまま `theme.ts` に置けば型が通る。
|
|
266
|
+
|
|
267
|
+
<details>
|
|
268
|
+
<summary><code>theme.ts</code>(全フィールドを埋めた完全例)</summary>
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
import { defineTheme, type SemanticColors } from "melta-app";
|
|
272
|
+
|
|
273
|
+
const light: SemanticColors = {
|
|
274
|
+
"bg-page": "#faf7f2", "bg-page-alt": "#f2ece2", "bg-surface": "#ffffff",
|
|
275
|
+
"bg-surface-alt": "#faf7f2", "text-heading": "#1a1410", "text-default": "#3c332b",
|
|
276
|
+
"text-muted": "#7a6f63", "border-default": "#e5ddd1", "border-strong": "#cfc3b2",
|
|
277
|
+
"input-bg": "#ffffff", "input-border": "#cfc3b2", "text-accent": "#b4531f",
|
|
278
|
+
"text-on-accent": "#ffffff",
|
|
279
|
+
};
|
|
280
|
+
const dark: SemanticColors = {
|
|
281
|
+
"bg-page": "#14100c", "bg-page-alt": "#1f1913", "bg-surface": "#1f1913",
|
|
282
|
+
"bg-surface-alt": "#14100c", "text-heading": "#f5efe6", "text-default": "#ded4c6",
|
|
283
|
+
"text-muted": "#a29686", "border-default": "#33291f", "border-strong": "#4a3d2f",
|
|
284
|
+
"input-bg": "#14100c", "input-border": "#4a3d2f", "text-accent": "#e0803f",
|
|
285
|
+
"text-on-accent": "#14100c",
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const theme = defineTheme({
|
|
289
|
+
id: "acme",
|
|
290
|
+
color: {
|
|
291
|
+
primary: {
|
|
292
|
+
"50": "#fdf5ef", "100": "#f9e6d6", "200": "#f2c9a9", "300": "#e8a674",
|
|
293
|
+
"400": "#dd8446", "500": "#c96a2b", "600": "#b4531f", "700": "#93401a",
|
|
294
|
+
"800": "#73331a", "900": "#5a2a18", "950": "#31150b",
|
|
295
|
+
},
|
|
296
|
+
body: "#3c332b",
|
|
297
|
+
semantic: { light, dark },
|
|
298
|
+
status: {
|
|
299
|
+
success: { base: "#2f7d4f", subtleLight: "#eaf6ee", textLight: "#256540",
|
|
300
|
+
subtleDark: "rgba(47,125,79,0.16)", textDark: "#7fc79a" },
|
|
301
|
+
warning: { base: "#a8720d", subtleLight: "#fdf3e0", textLight: "#8a5d0a",
|
|
302
|
+
subtleDark: "rgba(168,114,13,0.16)", textDark: "#e0b054" },
|
|
303
|
+
danger: { base: "#c0362c", subtleLight: "#fbeceb", textLight: "#9d2c24",
|
|
304
|
+
subtleDark: "rgba(192,54,44,0.16)", textDark: "#f08b83" },
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
typography: {
|
|
308
|
+
fontFamily: {}, // 未指定 = OS デフォルト(RN の fontFamily は string 一本。embed は expo-font 側の責務)
|
|
309
|
+
fontSize: {
|
|
310
|
+
xxs: { fontSize: 10, lineHeight: 14 }, xs: { fontSize: 12, lineHeight: 18 },
|
|
311
|
+
sm: { fontSize: 14, lineHeight: 21 }, base: { fontSize: 16, lineHeight: 26 },
|
|
312
|
+
lg: { fontSize: 18, lineHeight: 27 }, xl: { fontSize: 20, lineHeight: 28 },
|
|
313
|
+
"2xl": { fontSize: 24, lineHeight: 31 }, "3xl": { fontSize: 30, lineHeight: 36 },
|
|
314
|
+
},
|
|
315
|
+
fontWeight: { normal: "400", medium: "500", semibold: "600", bold: "700" },
|
|
316
|
+
letterSpacingRatio: { heading: -0.02, body: 0 }, // em 比率(px ではない)
|
|
317
|
+
},
|
|
318
|
+
spacing: { "1": 4, "2": 8, "3": 12, "4": 16, "5": 20, "6": 24, "8": 32, "10": 40, "12": 48, "14": 56, "16": 64 },
|
|
319
|
+
radius: { sm: 4, md: 8, lg: 16, full: 9999 },
|
|
320
|
+
elevation: {
|
|
321
|
+
none: { shadowColor: "#000000", shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0, shadowRadius: 0, elevation: 0 },
|
|
322
|
+
sm: { shadowColor: "#000000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 2 },
|
|
323
|
+
md: { shadowColor: "#000000", shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 6, elevation: 5 },
|
|
324
|
+
overlay: { shadowColor: "#000000", shadowOffset: { width: 0, height: 20 }, shadowOpacity: 0.1, shadowRadius: 25, elevation: 10 },
|
|
325
|
+
},
|
|
326
|
+
motion: {
|
|
327
|
+
duration: { fast: 150, normal: 200, slow: 300 },
|
|
328
|
+
easing: { default: [0.4, 0, 0.2, 1], in: [0.4, 0, 1, 1], out: [0, 0, 0.2, 1] },
|
|
329
|
+
},
|
|
330
|
+
zIndex: { base: 0, dropdown: 20, sticky: 30, overlay: 40, modal: 50 },
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
(tsc 6.0 / `strict: true` で型が通ることを fixture で確認済み。この theme 定義ファイル自体は
|
|
335
|
+
ブランドの生値を持つので、[lint の完全例](#動く完全例)のように `ignores` に入れる)
|
|
336
|
+
|
|
337
|
+
</details>
|
|
338
|
+
|
|
339
|
+
### 単一の配色しか持たないブランド
|
|
340
|
+
|
|
341
|
+
**単一の配色しか持たないブランド**(例: dark しか作らない)は、持っていない mode を **書かない**。
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
const theme = defineTheme({
|
|
345
|
+
id: "acme",
|
|
346
|
+
color: { /* … */ semantic: { dark: darkColors } }, // light は書かない
|
|
347
|
+
/* … */
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
- `useTheme().capabilities.colorScheme` が `"single-dark"` として導出される(宣言する欄は無い。`color.semantic` のキー集合がそのまま能力になる)
|
|
352
|
+
- OS が light を返しても **dark で描画する**(警告は出さない。light を作らないのは設計判断で、OS 設定は事故ではない)
|
|
353
|
+
- `forcedMode="light"` のように**対応していない mode を明示指定**した場合も描画は止めず clamp するが、開発時は `console.error` で1回報告する。light/dark トグル UI を出すなら `capabilities.colorScheme` を見て出し分けること
|
|
354
|
+
- `theme.color.semantic.light` を直接読むと、原因を名指しするエラーで落ちる(値を捏造して静かに間違った色を返すことはしない)。現在 mode の色は `useTheme().colors` から取る
|
|
355
|
+
|
|
356
|
+
> 現状 `color.primary` / `text-accent` / `elevation` / status の light 値は、単一 dark のテーマでも**必須のまま**。省略できるのは `color.semantic` の mode だけで、他の軸を「持たない」と宣言する仕組みは後続で入れる。
|
|
357
|
+
|
|
358
|
+
## サブパス(Icon / SafeArea)
|
|
359
|
+
|
|
360
|
+
subpath は Icon(`melta-app/icons`)/ SafeArea 差替(`melta-app/safe-area`)/
|
|
361
|
+
[lint plugin](#利用側コードの-lint-強制層melta-appeslint-plugin)(`melta-app/eslint-plugin`)の 3 つ。
|
|
362
|
+
|
|
363
|
+
### Icon(`melta-app/icons`)
|
|
364
|
+
|
|
365
|
+
Icon は唯一 `react-native-svg`(optional peerDependency)に依存するため、本体エントリから分離している。
|
|
228
366
|
本体 `melta-app` は依存ゼロのまま — Icon を使うアプリだけが以下を行う:
|
|
229
367
|
|
|
230
368
|
```bash
|
|
@@ -241,7 +379,7 @@ import { Icon } from "melta-app/icons";
|
|
|
241
379
|
グリフは Charcoal Icons(pixiv、Apache-2.0)の curated サブセット + Charcoal に無いグリフの
|
|
242
380
|
Material Symbols Rounded(Google、Apache-2.0)補完(`assets/icons/*.svg` →
|
|
243
381
|
`npm run generate:icons` で `src/icons/glyphs.ts` に codegen、commit 済みを配布)。
|
|
244
|
-
帰属表示は
|
|
382
|
+
帰属表示は [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md)。
|
|
245
383
|
|
|
246
384
|
### SafeArea の差し替え(`melta-app/safe-area`)
|
|
247
385
|
|
|
@@ -273,50 +411,48 @@ enableSafeAreaContext({ edges: ["top"] });
|
|
|
273
411
|
- safe-area と合成する padding は**数値のみ**サポート(`"5%"` 等の非数値は基底として扱えず、
|
|
274
412
|
対象 edge は inset 値に置き換わる)。RTL / `paddingStart`・`paddingEnd` は対応済み
|
|
275
413
|
|
|
276
|
-
|
|
414
|
+
## 設計の核
|
|
277
415
|
|
|
278
|
-
melta
|
|
279
|
-
|
|
416
|
+
- **契約は共有、実装は各最適**: tokens / 禁止ルール / component 契約は `melta-contracts`(JSON)が SSOT。melta-app に**手書きの token 正本**は持たない(`native-theme.ts` は契約からの生成物。二重管理を物理防止)。
|
|
417
|
+
- **公開 DS の純度を守る**: 汎用 UI プリミティブだけを置く。アプリ固有の UI(特定サービスの画面・機能・語彙)は**各アプリ側**に置き、ここには混ぜない。
|
|
418
|
+
- **依存最小**: 初期は RN `StyleSheet` 固定(nativewind / unistyles 等の runtime styling lib を入れない)。Storybook RN も使わず自前カタログ。
|
|
419
|
+
- **人間と AI の両方が読める**: variants / sizes / states / tokens / a11y はすべて契約の JSON。実装状態も `appStatus`(implemented / planned / not-planned)と `appMapping`(adapted = モバイル慣習への変換)が機械可読で、この README の表も showcase の表もそこから生成される。
|
|
420
|
+
- **ズレは CI が拾う**: ライブラリ内部の契約準拠は conformance テストが機械判定し、ドキュメントの腐りは drift 検査が落とす。利用側コードは consumer lint(`melta-app/eslint-plugin`)が**直接リテラル 4 類型を補助検査**する(変数・spread 経由は漏れる。純度の本丸は token 経由で書く習慣の側)。
|
|
421
|
+
- web 版には MCP サーバー(`melta-ds-mcp`)もあり、Claude Code / Cursor から契約・トークン・ルールを直接引ける(RN 対応は今後)。
|
|
280
422
|
|
|
281
|
-
|
|
282
|
-
// eslint.config.mjs
|
|
283
|
-
import { meltaPlugin } from "melta-app/eslint-plugin";
|
|
423
|
+
内部構造(ディレクトリ / トークン正規化表 / 実装ステータスの詳細 / CI)は [docs/architecture.md](./docs/architecture.md)。
|
|
284
424
|
|
|
285
|
-
|
|
286
|
-
{
|
|
287
|
-
plugins: { melta: meltaPlugin },
|
|
288
|
-
rules: {
|
|
289
|
-
"melta/no-raw-color": "error", // 生 hex/rgb/hsl → theme.color.*
|
|
290
|
-
"melta/no-raw-radius": "error", // borderRadius 数値直書き → theme.radius.*
|
|
291
|
-
"melta/no-raw-spacing": "warn", // padding/margin/gap 数値直書き(false positive あり)
|
|
292
|
-
"melta/no-raw-fontsize": "warn", // fontSize 数値直書き
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
];
|
|
296
|
-
```
|
|
425
|
+
## 制約と正直な範囲
|
|
297
426
|
|
|
298
|
-
-
|
|
299
|
-
-
|
|
300
|
-
-
|
|
301
|
-
|
|
427
|
+
- **検証済み構成は Expo 56 / RN 0.85 / React 19.2**。他の組み合わせは動く可能性が高いが検証していない
|
|
428
|
+
- **consumer lint は補助線**。検知するのは**直接リテラルの 4 類型**(color / radius / spacing / fontSize)だけで、変数経由・spread 経由は漏れる。純度の本丸は token 経由で書く習慣の側にある
|
|
429
|
+
- **conformance(ライブラリ内部の契約準拠)と consumer lint(利用側コードの検査)は別物**。前者は CI で機械判定、後者は消費者の flat config に組み込んで初めて効く
|
|
430
|
+
- **lint plugin は flat config 専用**(eslintrc からは登録できない)。base config(parser)も必須
|
|
431
|
+
- **0.x 系につき破壊的変更は minor で入る**。移行手順は [CHANGELOG](./CHANGELOG.md) に毎回書く
|
|
432
|
+
- **planned / not-planned のコンポーネントは実装されていない**([表](#コンポーネント)参照)。not-planned はモバイルで別の形に変換すべきものなので、今後も web と 1:1 にはならない
|
|
433
|
+
|
|
434
|
+
## 成熟度・メンテナンス
|
|
435
|
+
|
|
436
|
+
- **0.x**。API は安定に向かっているが、破壊的変更は minor で入る([CHANGELOG](./CHANGELOG.md) が変更の正本。npm に公開した版だけを記録する)
|
|
437
|
+
- **個人メンテナンスのプロジェクト**(tsubotax)。SLA・サポート窓口は無い。issue / PR は歓迎するが応答は best effort
|
|
438
|
+
- **現在の adopter は 1 つ**(非公開プロジェクトのモバイルアプリ)。dogfood はそこで回している。D2I は当初想定していた最初の adopter だが、mobile 側はまだ melta-app を導入していない(D2I の web 側が使っているのは melta-ui)
|
|
439
|
+
- React Native Directory 登録は [PR #2606](https://github.com/react-native-community/directory/pull/2606) がレビュー待ち
|
|
440
|
+
|
|
441
|
+
## セキュリティ・データ境界
|
|
302
442
|
|
|
303
|
-
|
|
443
|
+
lint も theme 生成もすべて**ローカル処理**で完結する。コード・トークン・生成物を外部へ送信しない。
|
|
444
|
+
telemetry / 使用状況の収集は無い。runtime 依存ゼロ(peerDependencies のみ)なので、
|
|
445
|
+
アプリのバンドルに melta 由来のネットワーク処理は入らない。
|
|
304
446
|
|
|
305
|
-
|
|
306
|
-
- ✅ `melta-contracts` を npm 依存として購読(recipes/app の styleRefs 同梱)
|
|
307
|
-
- ✅ conformance: 契約源 ↔ 生成メタ ↔ `__contract` 宣言の照合 + consumer テスト(契約 subset / token 実在 / contractVersion 同期)+ styleRefs conformance(全実装コンポーネント展開済み)+ RN mount smoke(light/dark × 全公開コンポーネント)
|
|
308
|
-
- ✅ ハーネス: design lint(CI `--max-warnings 0` + PostToolUse hook)/ drift 検査(README・catalog・allowlist 突合)/ installability ゲート(pack → tarball 実体検査 → fixture へ install → 本体 / icons / safe-area の import + typecheck → eslint plugin を実 import してルール 4 本の実在を照合 → exports の解決先確認。`npm run release` チェーンの必須ステップ)
|
|
309
|
-
- ✅ layout 6 個(Stack / Row / Screen / Header / Icon / Avatar)— dogfood 不足 1〜4 を解消、ProjectFeedScreen は公開 primitive だけで構成
|
|
310
|
-
- Screen の SafeArea は adapter registry 化済み: default は RN core SafeAreaView(依存ゼロ維持)、`melta-app/safe-area` の `enableSafeAreaContext()` で react-native-safe-area-context に差し替え可(optional peer)
|
|
311
|
-
- ✅ form / feedback 10 個(TextField / Toggle / Checkbox / Radio / Alert / Toast / Progress / Modal / ActionSheet / BottomSheet)— checkbox / radio は Pressable + 描画(svg 非依存)、ActionSheet / BottomSheet は select / dropdown の adapted 変換先の受け皿
|
|
312
|
-
- ✅ showcase(https://app.melta.tsubotax.com — melta-ui 様式シェル + 実 RN カタログの Live 埋め込み。表・統計は契約からビルド時生成)
|
|
313
|
-
- ✅ AI 入口: [llms.txt](https://app.melta.tsubotax.com/llms.txt)(契約から生成・drift 検査対象)+ [docs/patterns.md](docs/patterns.md)(フォームの組み方規範。スニペットは実コードと機械同期)
|
|
314
|
-
- ✅ lint 強制層の npm 配布(0.5.2): `melta-app/eslint-plugin` を公開 subpath 化。消費者プロジェクトの flat config に組めば、生値の直書きが消費者側でも lint で止まる
|
|
315
|
-
- ✅ [npm publish(0.5.2)](https://www.npmjs.com/package/melta-app)
|
|
316
|
-
- ⬜ React Native Directory 登録([PR #2606](https://github.com/react-native-community/directory/pull/2606) レビュー待ち)
|
|
447
|
+
## Learn more
|
|
317
448
|
|
|
318
|
-
|
|
449
|
+
- [docs/architecture.md](./docs/architecture.md) — ディレクトリ構成 / トークン正規化(web → RN)/ 実装ステータス詳細 / CI(このリポを触る人向け)
|
|
450
|
+
- [docs/patterns.md](./docs/patterns.md) — フォームの組み方規範 + コピペ可能スニペット(実コードと機械同期)
|
|
451
|
+
- [llms.txt](https://app.melta.tsubotax.com/llms.txt) — AI エージェント向けの入口(契約から生成)
|
|
452
|
+
- [CHANGELOG.md](./CHANGELOG.md) — 変更の正本(破壊的変更の移行手順つき)
|
|
453
|
+
- [melta-ui](https://github.com/tsubotax/melta-ui) — web 版(契約の SSOT・MCP サーバー・禁止ルールの本体)
|
|
454
|
+
- [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md) — Icon グリフの帰属表示
|
|
319
455
|
|
|
320
456
|
## License
|
|
321
457
|
|
|
322
|
-
MIT
|
|
458
|
+
MIT([LICENSE](./LICENSE))。同梱するアイコングリフの帰属表示は [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md)。
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# melta-app アーキテクチャ / 開発台帳
|
|
2
|
+
|
|
3
|
+
> このリポジトリを**触る人**向けの台帳。**使う人**向けの情報(Install / lint 強制層 / コンポーネント表 /
|
|
4
|
+
> テーマ注入 / 制約)は [README](../README.md) にある。ここは README から分離した詳細で、
|
|
5
|
+
> 内容は削っていない(README 側には要約とリンクだけを置く)。
|
|
6
|
+
|
|
7
|
+
## ディレクトリ
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
melta-app/
|
|
11
|
+
src/ ライブラリ本体(npm 配布物)
|
|
12
|
+
theme/
|
|
13
|
+
types.ts NativeTheme の型
|
|
14
|
+
native-theme.ts ⚠️ 自動生成(melta-contracts → RN 正規化済み)
|
|
15
|
+
index.ts theme エントリ(ThemeProvider / useTheme / nativeTheme)
|
|
16
|
+
primitives/ Text / Button / Tag / Metric / Stack / Row
|
|
17
|
+
components/ Card / Image / Surface / Skeleton / EmptyState / Screen / Header /
|
|
18
|
+
Avatar / TextField / Toggle / Checkbox / Radio / Alert / Toast /
|
|
19
|
+
Progress / Modal / ActionSheet / BottomSheet
|
|
20
|
+
icons/ Icon + glyphs(subpath エントリ melta-app/icons、react-native-svg 隔離)
|
|
21
|
+
contracts/
|
|
22
|
+
contract-types.ts ⚠️ 自動生成(契約メタ CONTRACTS + 型)
|
|
23
|
+
index.ts 公開エントリ
|
|
24
|
+
example/ カタログアプリ(Expo、publish 除外)
|
|
25
|
+
App.tsx / catalog/ / screens/
|
|
26
|
+
scripts/
|
|
27
|
+
generate-native-theme.ts melta-contracts/tokens.json → src/theme/native-theme.ts
|
|
28
|
+
generate-contract-types.ts 契約 JSON → src/contracts/contract-types.ts
|
|
29
|
+
check-drift.ts README / docs / catalog / allowlist の drift 検査(--write で heal)
|
|
30
|
+
check-installability.sh pack → install → import → typecheck の公開ゲート
|
|
31
|
+
lib/ token 正規化 + conformance / consumer テスト
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## トークン正規化(web → RN)
|
|
35
|
+
|
|
36
|
+
`scripts/lib/normalize-tokens.ts` が変換する。壊れやすい箇所はテスト必須。
|
|
37
|
+
|
|
38
|
+
| トークン | web | RN |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| color | hex / rgba | そのまま(semantic は light/dark mode 切替) |
|
|
41
|
+
| spacing / radius / fontSize | rem + px | px の数値を採用(RN は unitless) |
|
|
42
|
+
| shadow | CSS box-shadow | iOS shadow* + Android elevation に分解(1 ViewStyle に同居) |
|
|
43
|
+
| fontFamily | 配列 | 単一文字列(先頭採用、未ロード時 OS デフォルト fallback) |
|
|
44
|
+
| lineHeight | 比率("2.0") | `fontSize × ratio` を px 算出 |
|
|
45
|
+
| letterSpacing | em | **em ratio の数値で保持**(fontSize 相対のため token 単体で px 化不可。適用側が `fontSize × ratio` で解決) |
|
|
46
|
+
| motion duration | "150ms" | 150(数値) |
|
|
47
|
+
| motion easing | cubic-bezier | `[a,b,c,d]` tuple(`Easing.bezier(...)` に展開) |
|
|
48
|
+
|
|
49
|
+
## 開発
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install # melta-contracts(npm 公開済み)も devDependencies として入る
|
|
53
|
+
|
|
54
|
+
# theme + contract 型を生成(melta-contracts のノードを読む。
|
|
55
|
+
# 未 install 時のみ隣の melta-ui を fallback で読む開発モード)
|
|
56
|
+
npm run generate
|
|
57
|
+
|
|
58
|
+
# 変換・conformance・consumer テスト
|
|
59
|
+
npm test
|
|
60
|
+
|
|
61
|
+
# 実機 / シミュレータでカタログを確認
|
|
62
|
+
npm run ios # または android / web
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 実装ステータス(詳細)
|
|
66
|
+
|
|
67
|
+
- ✅ ライブラリ化(root=ライブラリ / example=カタログアプリ、peerDeps react + react-native、runtime deps ゼロ)
|
|
68
|
+
- ✅ `melta-contracts` を npm 依存として購読(recipes/app の styleRefs 同梱)
|
|
69
|
+
- ✅ conformance: 契約源 ↔ 生成メタ ↔ `__contract` 宣言の照合 + consumer テスト(契約 subset / token 実在 / contractVersion 同期)+ styleRefs conformance(全実装コンポーネント展開済み)+ RN mount smoke(light/dark × 全公開コンポーネント)
|
|
70
|
+
- ✅ ハーネス: design lint(CI `--max-warnings 0` + PostToolUse hook)/ drift 検査(README・docs・catalog・allowlist 突合)/ installability ゲート(pack → tarball 実体検査 → fixture へ install → 本体 / icons / safe-area の import + typecheck → eslint plugin を実 import してルール 4 本の実在を照合 → exports の解決先確認。`npm run release` チェーンの必須ステップ)
|
|
71
|
+
- ✅ layout 6 個(Stack / Row / Screen / Header / Icon / Avatar)— dogfood 不足 1〜4 を解消、ProjectFeedScreen は公開 primitive だけで構成
|
|
72
|
+
- Screen の SafeArea は adapter registry 化済み: default は RN core SafeAreaView(依存ゼロ維持)、`melta-app/safe-area` の `enableSafeAreaContext()` で react-native-safe-area-context に差し替え可(optional peer)
|
|
73
|
+
- ✅ form / feedback 10 個(TextField / Toggle / Checkbox / Radio / Alert / Toast / Progress / Modal / ActionSheet / BottomSheet)— checkbox / radio は Pressable + 描画(svg 非依存)、ActionSheet / BottomSheet は select / dropdown の adapted 変換先の受け皿
|
|
74
|
+
- ✅ showcase(https://app.melta.tsubotax.com — melta-ui 様式シェル + 実 RN カタログの Live 埋め込み。表・統計は契約からビルド時生成)
|
|
75
|
+
- ✅ AI 入口: [llms.txt](https://app.melta.tsubotax.com/llms.txt)(契約から生成・drift 検査対象)+ [patterns.md](./patterns.md)(フォームの組み方規範。スニペットは実コードと機械同期)
|
|
76
|
+
- ✅ lint 強制層の npm 配布(0.5.2): `melta-app/eslint-plugin` を公開 subpath 化。消費者プロジェクトの flat config に組めば、生値の直書きが消費者側でも lint で止まる。推奨 severity は `configs.recommended` で配布(0.5.3。消費者が手書きで写さない)
|
|
77
|
+
- ✅ 消費者プロジェクトでの実導入検証(2026-08-04): 別リポジトリの自プロジェクト(非公開 RN アプリ)に npm 経由で導入し、AI が違反コードを書いた直後に検出 → 修正フィードバック → 自己修正、のループを実測で確認。導入時に見つかった hook の欠陥(実行失敗時に無言で素通りする)は同日中に本体へ還元し、故障系を含む E2E 14 ケースで固定(0.5.3)
|
|
78
|
+
- ✅ [npm publish(0.5.3)](https://www.npmjs.com/package/melta-app)
|
|
79
|
+
- ⬜ React Native Directory 登録([PR #2606](https://github.com/react-native-community/directory/pull/2606) レビュー待ち)
|
|
80
|
+
|
|
81
|
+
## CI(.github/workflows/check.yml)
|
|
82
|
+
|
|
83
|
+
push / PR で以下を順に回す。CI には兄弟ディレクトリ(melta-ui)が存在しないため、`npm run generate`
|
|
84
|
+
が通ること自体が「npm 経路(melta-contracts)だけで生成できる」証明になっている。
|
|
85
|
+
|
|
86
|
+
1. `npm run generate` → 生成物の差分ゼロ(再生成漏れの検知)
|
|
87
|
+
2. `npm run typecheck`(lib + example + scripts)
|
|
88
|
+
3. `npm run lint`(`--max-warnings 0`)
|
|
89
|
+
4. `npm test`(consumer + conformance)
|
|
90
|
+
5. `npm run check:drift`(README / docs / catalog / allowlist)
|
|
91
|
+
6. `npm run test:rn`(RN mount smoke)
|
|
92
|
+
7. `npm run check:installability`(pack → install → import → typecheck)
|