Upgrade to Pro — share decks privately, control downloads, hide ads and more …

契約で守るコンパウンドプロダクトのデザインシステム

 契約で守るコンパウンドプロダクトのデザインシステム

Avatar for Yuji Yamaguchi

Yuji Yamaguchi

October 24, 2026

More Decks by Yuji Yamaguchi

Other Decks in Technology

Transcript

  1. 自己紹介 • 業務 ◦ プロダクトエンジニア・エンジニア採用・技術広報 • 経歴 ◦ ◦ ◦

    ◦ ◦ ◦ ◦ 1987/12 埼玉県本庄市生まれ 2011/04 ソフトバンクモバイル株式会社 2016/01 株式会社サイバーエージェント 2016/10 株式会社リクルートライフスタイル 2021/09 株式会社ラクス 2022/08 株式会社HRBrain 2025/07 Dress Code 株式会社 • プライベート ◦ ◦ マンガ・アニメ・ゲーム・映画・お酒 三児の父(中3, 中1, 年中) 山口 祐司 ぷーじ(@yug1224) 5
  2. 会社概要 Company Name / 会社名 Dress Code 株式会社 CEO /

    代表取締役 江尻 祐樹 Date of establishment / 設立年月 2024年9月(正式創業 2025年4月) Location / 所在地 東京都中央区築地2-1-4 銀座PREX East 8F Member / メンバー数 56名 Pre Seed&Seed Round Number of companies Number of countries 14.1億円 250+社 5カ国 資金調達を実施 が利用中 で事業を展開 © Dress Code Inc. All rights reserved. 7
  3. 9

  4. 機能を足すのではなく、ひとつにする 作る機能が1つ 作る人 作る機能が複数 作る人 保存 多くのSaaSは「機能を足しませんか」と言うが、 保存 保存 保存

    保存 保存 私たちはその逆 採用・労務・人事・IT・総務を 一つの基盤にまとめている 同じ「ボタン」でも、 画面を作る人が増えると、 幅も角も色も別のものになっていく 同じ部品が別の使われ方になる 「誰が作っても同じ画面になる」が要件になる 10
  5. 業務に必要な観点3つは、フロントエンドでも必要 業務システムでは フロントエンドでは データと処理の形を揃える 画面の形をスキーマの契約で決める Structure 構造化 Rules ルール SSOT

    単一の正 任せるための判断基準を定める データの正を 一か所に決める 規約を、機械が判定できる 検査として書く 同じバージョンを全員に配る 11
  6. Schema-Driven UI のデザインシステム 画面を手で組まず、スキーマから描く。スキーマの形は契約で決めてある 契約 各チームが書く サーバーが配る 外部APIから来る 形を決める {

    ... } Data Table スキーマ レンダラ 自動で生成する 入口 画面 契約は zod で書いた型。スキーマの形と action の名前を一か所で決めていて、JSON Schema にも射影できる 入口が4つでも、レンダラが受け取るのは契約に通ったスキーマだけ 誰が書いたスキーマでも、同じ画面になる 13
  7. 契約の実物は zod の型 // contract/envelope.ts export const viewSchema = z.discriminatedUnion('viewKind',

    [ tableSchema, detailSchema, formSchema, calendarSchema, ganttSchema, settingsSchema, ... ]) 契約が dresscode-ui の本体資産 React のコンポーネントは契約の一つのレンダラにすぎない export const schemaEnvelope = envelopeHeader.extend({ view: viewSchema, }) // contract/table.ts export const columnSchema = z.object({ field: z.string().min(1), label: z.string(), cellType: z.union([cellType, z.string()]), sortable: z.boolean(), align: z.union([columnAlign, z.null()]).default(null), }) react を import しない contract と core が react に依存していないことを、 dependency-cruiser が毎回検査する 知らない値が来ても落とさない 未知の cellType は契約が string として通し、FallbackCell で描 く。union にメンバーを足しても後方互換 この zod の型が契約の実体 契約は JSON Schema / OpenAPI に射影できる。配るときはドキュメントと同じバージョンで出している 14
  8. 建物にたとえた6段階の構造 room 部屋 部品 99個の内訳 route を作る単位 furniture 家具 形が決まった組み合わせ

    equipment partition 間仕切り 開閉して仕切る 使える 設備 操作を受けて値を生む structure 静的な骨格 躯体 decor 装飾 静的な表示 room 16 furniture 17 partition 10 equipment 34 structure 7 decor 15 参照できない route は room を組み合わせて建てる house(家)。テストケースは約2,000 15
  9. 6段階に実際に入っているもの room 部屋 data-table / calendar / form / gantt

    部品1つは8点セット furniture 家具 alert-card / chart-stat-card partition 間仕切り dialog / drawer / popover / launcher equipment 設備 button / combobox / autocomplete structure 躯体 card / empty / scroll-area / separator decor 装飾 badge / avatar / icon / sparkline stem のディレクトリ + index.ts / .tsx / .recipe.ts / .messages.ts / .fixture.tsx / .test.tsx / .doc.md。揃っていないと構造テ ストが落ちる 段をまたぐ参照は止まる 下の段から上の段への import は dependency-cruiser が落とす。6段の分類は 「この部品は建物の何にあたるか」で決める 16
  10. 描画される UI を CI が検証する PR 書き方と型 構造と挙動 配布物と見た目 lint

    boundary lint publish health room の className 直書き 下の段から上への参照 exports と型の解決 format unit VRT 整形のずれ ファイルの欠落とキー数 同じURLなら同じ画像 typecheck interaction a11y トークン以外の値 操作したときの挙動 アクセシビリティの違反 merge どれか一つでも落ちれば merge できない 文書に残すのは、機械が判定できない判断だけ 17
  11. private registry で唯一のバージョンを配る dresscode-ui /data-table private registry /form publish /contract

    /core /bindings 上段は room ごとの置き場(16本) 下段は room をまたぐ層 GitHub Packages スコープ付き install dresscode-frontend プロダクトの画面を 作るリポジトリ v0.1.0 青い2つは UI ライブラリを知らない publish する前に、 ドキュメントと契約スキーマも、 exports と型の解決を機械が検査する 同じバージョンで出している 18
  12. host を渡し、action で受け取るだけ host を渡す dresscode-frontend スキーマの取得・行の取得・操作の受け口の3つ dresscode-ui action が返る

    レンダラ アプリ row.open という名前と requestId。関数は渡さない const host = createDataTableHost({ fetchSchema: (viewKey) => api.get(...), fetchRows: (viewKey, query) => api.get(...), onAction: (action) => { 関数をプロパティで渡さないので、 画面定義は JSON のまま運べる if (action.type === 'row.open') router.push(`/devices/${action.rowId}`) }, }) <DataTableRoom host={host} viewKey="devices" /> 描画中に Date.now() と Math.random() は使わない 19
  13. action は43種類、形はすべて同じ agentRun.retry form.cancel range.change agentRun.stop form.submit reference.open approval.answer item.action

    request.answer artifact.action markdown.save request.reask artifact.confirm message.feedback row.open artifact.open message.regenerate scale.change artifact.revise message.send section.edit requestId は全 union で共有 bar.open model.select section.save cell.commit node.open slot.select check.answer node.toggleCollapse snapshot.add 契約の actionRequestId。同じ操作の再送は同じ id、別の操作は別の id。host が二重 emit を捨てられる。描画中に Date.now() と Math.random() を使わないのはこのため context.clear page.change snapshot.remove context.dismiss pageSize.change sort.change edits.save period.change tab.select action は意図の報告 event.open permission.answer など filter.change queue.cancel 開くかどうか・楽観更新するかは host の判断。room は「押され た」としか言わない どれも同じ形 { type, requestId, …その action 固有の値 } room ごとに名前は違うが、形は同じ。host は type で分岐するだけ 20
  14. 今後の展望 UIの拡充 DRESS CODE 全体への適用 いま 既存プロダクトから切り出している途中 いま 一部の画面から入れ始めた段階 これから

    足りない部品を足していく これから 全画面へ広げていく 用語の検査も機械に渡す 差し替えを実証する いま 人が PR で見つけて直している いま レンダラの実装は見本が一つだけ これから 表記のゆれも機械に渡す これから 契約が同じなら差し替えられると示す 21
  15. まとめ • コンパウンドプロダクトでは、「誰が作っても同じ画面になる」ことが必要 • DRESS CODE では Schema-Driven UI の実装を進めている

    ◦ 建物にたとえた6段階の構造で整理している ◦ ルールは機械が判定し、描画される UI を CI で検証する ◦ 文書に残すのは、機械が判定できない判断だけ ◦ 構造は契約で決め、同じバージョンを全員に配る ◦ 使う側は host を渡し、action で受け取るだけ 23