@mawaru/sdk 0.40.0 → 0.41.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.
Files changed (56) hide show
  1. package/_schemas/graph.ts +0 -5
  2. package/_schemas/human-manifest.test.ts +39 -101
  3. package/_schemas/human-manifest.ts +25 -152
  4. package/_schemas/port-spec.test.ts +33 -92
  5. package/_schemas/port-spec.ts +35 -44
  6. package/dist/_cli/login.js +15 -1
  7. package/dist/_cli/previewApi.d.ts +1 -1
  8. package/dist/_cli/previewCommand.d.ts +0 -8
  9. package/dist/_cli/previewCommand.js +37 -95
  10. package/dist/_cli/previewFiles.d.ts +1 -2
  11. package/dist/_cli/previewFiles.js +18 -42
  12. package/dist/_schemas/graph.d.ts +0 -15
  13. package/dist/_schemas/graph.js +0 -5
  14. package/dist/_schemas/human-manifest.d.ts +6 -24
  15. package/dist/_schemas/human-manifest.js +24 -116
  16. package/dist/_schemas/port-spec.d.ts +1 -4
  17. package/dist/_schemas/port-spec.js +25 -34
  18. package/dist/cli.js +1 -2
  19. package/dist/typegen.d.ts +2 -2
  20. package/dist/typegen.js +21 -21
  21. package/dist/validate.js +9 -7
  22. package/dist/views/_humanProps.d.ts +10 -0
  23. package/dist/views/_humanProps.js +11 -0
  24. package/dist/views/{index.d.ts → components.d.ts} +0 -1
  25. package/dist/views/components.js +12 -0
  26. package/dist/views/host.d.ts +8 -3
  27. package/dist/views/host.js +5 -5
  28. package/dist/views/human.d.ts +49 -68
  29. package/dist/views/human.js +5 -6
  30. package/dist/views/protocol.d.ts +13 -3
  31. package/dist/views/protocol.js +2 -2
  32. package/dist/views/runtime.d.ts +2 -3
  33. package/dist/views/runtime.js +24 -24
  34. package/dist/views/sample.d.ts +22 -0
  35. package/dist/views/sample.js +38 -0
  36. package/docs/development.md +30 -31
  37. package/package.json +5 -1
  38. package/skills/create-loop/SKILL.md +3 -2
  39. package/templates/CLAUDE.md +2 -0
  40. package/views/_humanProps.ts +27 -0
  41. package/views/components.ts +16 -0
  42. package/views/host.ts +9 -5
  43. package/views/human.ts +71 -79
  44. package/views/protocol.ts +29 -6
  45. package/views/runtime.tsx +36 -45
  46. package/views/sample.tsx +94 -0
  47. package/dist/_cli/openBrowser.d.ts +0 -1
  48. package/dist/_cli/openBrowser.js +0 -15
  49. package/dist/views/_approval/ApprovalBar.d.ts +0 -20
  50. package/dist/views/_approval/ApprovalBar.js +0 -74
  51. package/dist/views/_approvalActions.d.ts +0 -18
  52. package/dist/views/_approvalActions.js +0 -10
  53. package/dist/views/index.js +0 -12
  54. package/views/_approval/ApprovalBar.tsx +0 -239
  55. package/views/_approvalActions.tsx +0 -31
  56. package/views/index.ts +0 -16
package/views/human.ts CHANGED
@@ -1,91 +1,83 @@
1
1
  import type { ReactNode } from "react"
2
2
  import type { JsonSchema } from "../_schemas/node.js"
3
+ import type { FileRef } from "./_parts/fileRef.js"
3
4
 
4
- // human ノードの実装(nodes/human/<dir>/index.tsx)の約束(`@mawaru/sdk/human`)。承認画面(ビュー)は React の
5
- // コンポーネント1つで、承認画面の上の帯より下の全部(書類と承認バー)を描く。右上の閉じるボタン・差し戻し後の帯・
6
- // 差し戻しと追加の出口のダイアログ・認可・decision の POST・Broadcast の購読はホスト(mawaru の承認画面)に残る
7
- // (docs/tasks/wip/承認ビューの顧客repo配置.md の「承認バーもビューが描く」)。
5
+ // human ノードの実装(顧客 repo の nodes/human/<dir>/index.tsx)の約束(`@mawaru/sdk/human`)。
6
+ // human ノードは「input を受け取り、人の操作で出口(approve・reject・追加の出口)に値を出す」。
7
+ // program の run(input) が { <出口ポートのkey>: <データ> } を返すのと同じ形で、human はそれを人が画面で選ぶ。
8
8
  //
9
- // ctx に入るのは JSON で表現できる値だけ(React 要素・クラスインスタンス・ホストの関数は無い)。
10
- // 顧客 repo のビューは sandbox iframe の中で動き、ctx は postMessage で届く。
11
- // ホストに頼みたいこと(ファイルの URL・アップロード・コピー)は useViewBridge()、承認の操作は useApprovalActions() から
9
+ // 実装は React のコンポーネント1つで、承認画面の上の帯より下を全部描く。mawaru が esbuild でまとめ、
10
+ // sandbox iframe の中で動かす(mawaru のログイン・API・fetch には届かない)。
11
+ // 局面・担当・全員承認の進み具合は mawaru の承認画面の帯が出し、実装が描かれるのは承認待ちのときだけ。
12
12
  //
13
- // TIn は実装が宣言(config.json の inputs.in)した形。宣言と型引数が食い違ってもコンパイルは通るので、
14
- // **一致はビュー実装の責任**(typegen の types.d.ts を使うと揃う)
13
+ // 型は mawaru typegen が config.json から作る types.d.ts(Input / Outputs / Output / Refs。program と同じ)を使う:
14
+ // export default defineHuman<Input, Output, Refs>(({ input, out, ctx }) => …)
15
15
 
16
- /** 承認画面の局面:承認待ち/承認済み/差し戻し後(AI 再実行待ち)/失敗/キャンセル */
17
- export type ApprovalPhase =
18
- | "waiting"
19
- | "approved"
20
- | "rejected"
21
- | "failed"
22
- | "cancelled"
23
-
24
- /**
25
- * 承認バーの材料。部品の `ApprovalBar` はこれをそのまま使う。承認バーを自分で書くときに読む。
26
- */
27
- export type ApprovalDecisionContext = {
28
- /** 見ている人が今決定できる(担当で承認待ち)。全員の承認が要るノードでも true(値を直せるかは `editable`) */
29
- canDecide: boolean
30
- /** 承認したあとに流れる先のノード名(つながっていなければ null) */
31
- approveTargetName: string | null
32
- /** 差し戻し先のノード名(無ければ差し戻せない) */
33
- rejectTargetName: string | null
34
- /** 承認・差し戻し以外の出口。`needsInput` は、今の値の型が出口と合わず、値を入れるダイアログが要るもの */
35
- exits: { key: string; name: string; needsInput: boolean }[]
36
- /** 担当でない人が開いたときの、担当の名前(担当なら null) */
37
- otherAssignees: string[] | null
38
- /** 全員の承認が要るときの進み具合(`approvedByMe` は自分がもう承認したか)。要らなければ null */
39
- progress: { done: number; total: number; approvedByMe: boolean } | null
40
- /** 失敗した step の理由(失敗していなければ null) */
41
- failedReason: string | null
16
+ export type HumanProps<
17
+ TInput = unknown,
18
+ TOutput = Record<string, unknown>,
19
+ TRefs = Record<string, unknown>,
20
+ > = {
21
+ /** 届いた値(in ポートの値= step の input) */
22
+ input: TInput
23
+ /**
24
+ * 人の操作を mawaru に渡す。`{ <出口ポートのkey>: <データ> }`(program の run の戻り値と同じ1キーのオブジェクト)。
25
+ * 人がボタンを押した直後の呼び出しだけを受け付ける(開いただけで決定する画面は書けない)。
26
+ * 送れたら承認画面は run の画面へ戻る。断られたら(担当でない・もう決まっている など)理由つきで失敗する
27
+ */
28
+ out: (output: TOutput) => Promise<void>
29
+ /** input 以外の材料(program の run の ctx と同じ位置づけ) */
30
+ ctx: {
31
+ input: {
32
+ /** input の型(in ポートの JSON Schema) */
33
+ schema: JsonSchema
34
+ /** input に含まれるファイルを表示する URL を取る(sandbox の中からは mawaru のファイルを直接読めない) */
35
+ fetchFileUrl: (file: FileRef) => Promise<string>
36
+ }
37
+ output: {
38
+ /** 出口ごとの型(out ポートの key → JSON Schema。approve・reject・追加の出口) */
39
+ schema: Record<string, JsonSchema>
40
+ /** 出す値に入れるファイル(差し戻しの参考ファイルなど)を上げて、参照を受け取る */
41
+ uploadFile: (file: File) => Promise<FileRef>
42
+ }
43
+ clipboard: {
44
+ /** クリップボードに書く(sandbox の中では使えない navigator.clipboard.writeText の代わり) */
45
+ writeText: (text: string) => Promise<void>
46
+ }
47
+ /**
48
+ * ループ変数(この step が始まった時点の値。program の ctx.refs と同じ)。
49
+ * 予約キー `input` が run の起動入力
50
+ */
51
+ refs: TRefs
52
+ step: {
53
+ /** 見ている人がこの step の担当か(担当でなければ out は断られる) */
54
+ isAssignee: boolean
55
+ /**
56
+ * 値を直してよいか:担当で、全員承認のノードでないとき true
57
+ * (全員承認は全員が同じ値を承認するので、届いた値のまま決める)
58
+ */
59
+ editable: boolean
60
+ }
61
+ }
42
62
  }
43
63
 
44
- /** mawaru から承認ビューへ渡す材料 */
45
- export type ApprovalViewContext<TIn = unknown> = {
46
- /** 届いた値(step の input そのもの)。ビューで直した値はビューの state に持つ */
47
- output: TIn
48
- /** `output` の型= human の in ポートの型(ビューの宣言が写っている)。`SchemaForm` に渡すとフォームになる */
49
- outputSchema: JsonSchema | null
50
- /** ループ変数(この step が始まった時点の写し)。予約キー `input` が run の起動入力 */
51
- refs: unknown
52
- /** この human ノードの名前 */
53
- nodeName: string
54
- /** 局面 */
55
- phase: ApprovalPhase
56
- /** 値を直してよい(承認待ちで、見ている人が担当。全員の承認が要るノードでは false) */
57
- editable: boolean
58
- /** 承認バーの材料 */
59
- decision: ApprovalDecisionContext
60
- }
64
+ /** mawaru が承認画面に描く、human ノードの画面(React のコンポーネント) */
65
+ export type HumanView<
66
+ TInput = unknown,
67
+ TOutput = Record<string, unknown>,
68
+ TRefs = Record<string, unknown>,
69
+ > = (props: HumanProps<TInput, TOutput, TRefs>) => ReactNode
61
70
 
62
71
  /**
63
- * 承認ビュー:ctx を受けて承認画面(上の帯より下の全部)を描く React コンポーネント。
64
- * 承認バーは `<ApprovalBar ctx={ctx} />` を置く(置かなければ承認バーは出ない)。
72
+ * human ノードの実装を定義する:input と out を受けて描く関数を渡すと、画面を返す。
73
+ * React では props を受けて描く関数がそのままコンポーネントなので、型を付けて返すだけ
65
74
  */
66
- // 引数を双変にする(メソッドの形で宣言する):どの TIn のビューも ApprovalView<unknown> として持てるようにするため
67
- export type ApprovalView<TIn = unknown> = {
68
- bivariance(props: { ctx: ApprovalViewContext<TIn> }): ReactNode
69
- }["bivariance"]
70
-
71
- /** 承認ビューを定義する。型を付けるためだけの関数(値はそのまま返す) */
72
- export const defineApprovalView = <TIn = unknown>(
73
- view: ApprovalView<TIn>,
74
- ): ApprovalView<TIn> => view
75
-
76
- /** 手元プレビュー(npx mawaru preview human/<dir>)のケース。nodes/human/<dir>/cases.ts の export default */
77
- export type ApprovalViewCase = {
78
- name: string
79
- /** `ctx.output`(= step の input) */
80
- input: unknown
81
- refs?: unknown
82
- /** 省略時は name */
83
- nodeName?: string
84
- /** 省略時は承認待ち */
85
- phase?: ApprovalPhase
86
- }
75
+ export const defineHuman = <
76
+ TInput = unknown,
77
+ TOutput = Record<string, unknown>,
78
+ TRefs = Record<string, unknown>,
79
+ >(
80
+ render: (props: HumanProps<TInput, TOutput, TRefs>) => ReactNode,
81
+ ): HumanView<TInput, TOutput, TRefs> => render
87
82
 
88
- export { ApprovalBar } from "./_approval/ApprovalBar.js"
89
- export { type ApprovalActions, useApprovalActions } from "./_approvalActions.js"
90
- // 部品キット(土台)もここから import できる
91
- export * from "./index.js"
83
+ export type { FileRef }
package/views/protocol.ts CHANGED
@@ -1,29 +1,52 @@
1
- // sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru の画面)とのメッセージの形。
1
+ import type { JsonSchema } from "../_schemas/node.js"
2
+
3
+ // sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru の承認画面)とのメッセージの形。
2
4
  // **公開 API ではない**:iframe の中の実行部分とホストは同じデプロイの mawaru のコード(まとめるときに
3
- // その時点の版を同梱する)なので、いつ変えてもよい(docs/tasks/wip/承認ビューの顧客repo配置.md の「互換の管理」)。
5
+ // その時点の版を同梱する)なので、いつ変えてもよい。
4
6
  // iframe に入る JS を小さく保つため、ここには型と定数だけを置く(ホストの検査は host.ts)
5
7
 
6
8
  export const VIEW_MESSAGE_TAG = "mawaru:view"
7
9
 
8
10
  export type ThemeMode = "light" | "dark"
9
11
 
12
+ // human ノードの実装に渡す材料のうち、JSON で送れるもの(関数はホストへの頼みごととして iframe の中で組み立てる)
13
+ export type HumanViewData = {
14
+ input: unknown
15
+ inputSchema: JsonSchema
16
+ outputSchema: Record<string, JsonSchema>
17
+ refs: unknown
18
+ isAssignee: boolean
19
+ editable: boolean
20
+ }
21
+
22
+ // ビューからホストへの頼みごと(HumanProps の関数と、部品が裏で使う口)
23
+ export type HumanRequestMethod =
24
+ | "out"
25
+ | "fetchFileUrl"
26
+ | "uploadFile"
27
+ | "writeText"
28
+
10
29
  // ビュー → ホスト
11
30
  export type ViewToHost =
12
31
  | { tag: typeof VIEW_MESSAGE_TAG; type: "ready" }
13
- // 描画の例外(ホストは標準ビューに差し替える)
32
+ // 描画の例外(ホストはエラーと理由を出す)
14
33
  | { tag: typeof VIEW_MESSAGE_TAG; type: "error"; message: string }
15
- // ホストに頼む(fileUrl / uploadFile / copyText、承認ビューは decide / openRejectDialog / openExitDialog / close も)
16
34
  | {
17
35
  tag: typeof VIEW_MESSAGE_TAG
18
36
  type: "request"
19
37
  id: number
20
- method: string
38
+ method: HumanRequestMethod
21
39
  args: unknown
22
40
  }
23
41
 
24
42
  // ホスト → ビュー
25
43
  export type HostToView =
26
- | { tag: typeof VIEW_MESSAGE_TAG; type: "ctx"; ctx: unknown; mode: ThemeMode }
44
+ | {
45
+ tag: typeof VIEW_MESSAGE_TAG
46
+ type: "data"
47
+ data: HumanViewData
48
+ mode: ThemeMode
49
+ }
27
50
  | {
28
51
  tag: typeof VIEW_MESSAGE_TAG
29
52
  type: "response"
package/views/runtime.tsx CHANGED
@@ -1,24 +1,19 @@
1
1
  import { Component, type ReactNode } from "react"
2
2
  import { createRoot } from "react-dom/client"
3
- import {
4
- type ApprovalActions,
5
- ApprovalActionsProvider,
6
- } from "./_approvalActions.js"
7
3
  import { ViewBridgeProvider } from "./_bridge.js"
4
+ import { type HumanOps, humanPropsOf } from "./_humanProps.js"
8
5
  import type { FileRef } from "./_parts/fileRef.js"
9
- import type { ApprovalView, ApprovalViewContext } from "./human.js"
6
+ import type { HumanView } from "./human.js"
10
7
  import {
11
8
  type HostToView,
12
- type ThemeMode,
9
+ type HumanRequestMethod,
10
+ type HumanViewData,
13
11
  VIEW_MESSAGE_TAG,
14
12
  type ViewToHost,
15
13
  } from "./protocol.js"
16
14
 
17
- // sandbox iframe の中の実行部分(docs/tasks/wip/承認ビューの顧客repo配置.md の「隔離」)。
18
- // mawaru が顧客 repo のビューをまとめるとき、入口からここの runXxxView を呼ぶ。
19
- // ホストとのメッセージの形は protocol.ts(公開 API ではない)
20
-
21
- // ---- iframe の中 ----
15
+ // sandbox iframe の中の実行部分。mawaru が顧客 repo の human ノードの実装をまとめるとき、
16
+ // 入口から runHumanView を呼ぶ。ホストとのメッセージの形は protocol.ts(公開 API ではない)
22
17
 
23
18
  // union のまま tag だけを外す(Omit は union を潰す)
24
19
  type WithoutTag<T> = T extends unknown ? Omit<T, "tag"> : never
@@ -35,7 +30,7 @@ const pending = new Map<
35
30
  number,
36
31
  { resolve: (value: unknown) => void; reject: (err: Error) => void }
37
32
  >()
38
- const ctxListeners = new Set<(ctx: unknown, mode: ThemeMode) => void>()
33
+ const dataListeners = new Set<(data: HumanViewData) => void>()
39
34
 
40
35
  const onHostMessage = (event: MessageEvent) => {
41
36
  if (event.source !== window.parent) return
@@ -49,14 +44,17 @@ const onHostMessage = (event: MessageEvent) => {
49
44
  else waiting.reject(new Error(message.error))
50
45
  return
51
46
  }
52
- if (message.type === "ctx") {
47
+ if (message.type === "data") {
53
48
  document.documentElement.dataset.mode = message.mode
54
- for (const listener of ctxListeners) listener(message.ctx, message.mode)
49
+ for (const listener of dataListeners) listener(message.data)
55
50
  }
56
51
  }
57
52
 
58
53
  // ホストに頼む(返事が来るまで待つ)
59
- export const requestHost = <T,>(method: string, args: unknown): Promise<T> =>
54
+ const requestHost = <T,>(
55
+ method: HumanRequestMethod,
56
+ args: unknown,
57
+ ): Promise<T> =>
60
58
  new Promise<T>((resolve, reject) => {
61
59
  const id = nextRequestId++
62
60
  pending.set(id, {
@@ -66,13 +64,22 @@ export const requestHost = <T,>(method: string, args: unknown): Promise<T> =>
66
64
  post({ type: "request", id, method, args })
67
65
  })
68
66
 
69
- const bridge = {
70
- fileUrl: (file: FileRef) => requestHost<string>("fileUrl", { file }),
67
+ const ops: HumanOps = {
68
+ out: (output) => requestHost<void>("out", { output }),
69
+ fetchFileUrl: (file: FileRef) =>
70
+ requestHost<string>("fetchFileUrl", { file }),
71
71
  uploadFile: (file: File) => requestHost<FileRef>("uploadFile", { file }),
72
- copyText: (text: string) => requestHost<void>("copyText", { text }),
72
+ writeText: (text: string) => requestHost<void>("writeText", { text }),
73
+ }
74
+
75
+ // 部品(FilePreview・SchemaForm のファイル欄)が裏で使う口も、同じ頼みごとにつなぐ
76
+ const bridge = {
77
+ fileUrl: ops.fetchFileUrl,
78
+ uploadFile: ops.uploadFile,
79
+ copyText: ops.writeText,
73
80
  }
74
81
 
75
- // 描画の例外をホストへ知らせる(ホストは標準ビューに差し替える)
82
+ // 描画の例外をホストへ知らせる(ホストはエラーと理由を出す)
76
83
  class ReportErrors extends Component<
77
84
  { children: ReactNode },
78
85
  { failed: boolean }
@@ -89,9 +96,9 @@ class ReportErrors extends Component<
89
96
  }
90
97
  }
91
98
 
92
- // iframe の中の共通の準備:ホストからのメッセージ・例外の報告。
93
- // 最初の ctx が届いたら render を呼ぶ(以降の ctx も同じ render に渡す)
94
- const startView = (render: (ctx: unknown) => ReactNode) => {
99
+ // 実装はコンポーネント1つ。状態(直した値など)は実装が useState で持ち、材料が届き直しても
100
+ // 同じコンポーネントとして描き直すので保たれる
101
+ export const runHumanView = (View: HumanView) => {
95
102
  window.addEventListener("message", onHostMessage)
96
103
  window.addEventListener("error", (event) =>
97
104
  post({ type: "error", message: event.message }),
@@ -102,31 +109,15 @@ const startView = (render: (ctx: unknown) => ReactNode) => {
102
109
  const container = document.getElementById("root")
103
110
  if (!container) throw new Error("#root がありません")
104
111
  const root = createRoot(container)
105
- ctxListeners.add((ctx) =>
112
+ dataListeners.add((data) => {
113
+ const props = humanPropsOf(data, ops)
106
114
  root.render(
107
115
  <ViewBridgeProvider bridge={bridge}>
108
- <ReportErrors>{render(ctx)}</ReportErrors>
116
+ <ReportErrors>
117
+ <View {...props} />
118
+ </ReportErrors>
109
119
  </ViewBridgeProvider>,
110
- ),
111
- )
120
+ )
121
+ })
112
122
  post({ type: "ready" })
113
123
  }
114
-
115
- // ---- 承認ビュー ----
116
-
117
- const actions: ApprovalActions = {
118
- decide: (port, value) => requestHost<void>("decide", { port, value }),
119
- openRejectDialog: () => requestHost<void>("openRejectDialog", {}),
120
- openExitDialog: (key, value) =>
121
- requestHost<void>("openExitDialog", { key, value }),
122
- close: () => requestHost<void>("close", {}),
123
- }
124
-
125
- // ビューはコンポーネント1つ。状態(直した値・選択など)はビューが useState で持ち、ctx が変わっても
126
- // (局面・担当の更新)同じコンポーネントとして描き直すので保たれる
127
- export const runApprovalView = (View: ApprovalView) =>
128
- startView((ctx) => (
129
- <ApprovalActionsProvider value={actions}>
130
- <View ctx={ctx as ApprovalViewContext} />
131
- </ApprovalActionsProvider>
132
- ))
@@ -0,0 +1,94 @@
1
+ import { useState } from "react"
2
+ import { defineHuman } from "./human.js"
3
+
4
+ // サンプル:AI が下書きした返信を、人が直してから送る(approve)か、理由を書いて差し戻す(reject)。
5
+ // 顧客 repo では Input / Outputs / Output は typegen が config.json から作る(nodes/human/<dir>/types.d.ts)。
6
+ // ここでは同じものを手で書いている
7
+
8
+ type Input = { subject: string; body: string }
9
+
10
+ type Outputs = {
11
+ approve: { subject: string; body: string }
12
+ reject: { prompt: string }
13
+ }
14
+
15
+ type Output =
16
+ | { approve: Outputs["approve"]; reject?: never }
17
+ | { reject: Outputs["reject"]; approve?: never }
18
+
19
+ export default defineHuman<Input, Output>(({ input, out }) => {
20
+ const [reply, setReply] = useState(input)
21
+ const [prompt, setPrompt] = useState("")
22
+ return (
23
+ <div
24
+ style={{
25
+ maxWidth: "720px",
26
+ margin: "0 auto",
27
+ padding: "var(--space-8) var(--space-6)",
28
+ display: "flex",
29
+ flexDirection: "column",
30
+ gap: "var(--space-3)",
31
+ }}
32
+ >
33
+ <input
34
+ value={reply.subject}
35
+ onChange={(e) => setReply({ ...reply, subject: e.target.value })}
36
+ style={{
37
+ padding: "var(--space-2) var(--space-3)",
38
+ border: "var(--border-w) solid var(--border)",
39
+ borderRadius: "var(--radius-s)",
40
+ background: "var(--surface)",
41
+ color: "var(--ink)",
42
+ font: "inherit",
43
+ }}
44
+ />
45
+ <textarea
46
+ value={reply.body}
47
+ onChange={(e) => setReply({ ...reply, body: e.target.value })}
48
+ style={{
49
+ minHeight: "16em",
50
+ padding: "var(--space-2) var(--space-3)",
51
+ border: "var(--border-w) solid var(--border)",
52
+ borderRadius: "var(--radius-s)",
53
+ background: "var(--surface)",
54
+ color: "var(--ink)",
55
+ font: "inherit",
56
+ }}
57
+ />
58
+ <div style={{ display: "flex", justifyContent: "flex-end" }}>
59
+ <button
60
+ type="button"
61
+ className="primary"
62
+ disabled={reply.body.trim() === ""}
63
+ onClick={() => out({ approve: reply })}
64
+ >
65
+ この内容で送信
66
+ </button>
67
+ </div>
68
+ <div style={{ display: "flex", gap: "var(--space-2)" }}>
69
+ <input
70
+ value={prompt}
71
+ placeholder="差し戻しの理由"
72
+ onChange={(e) => setPrompt(e.target.value)}
73
+ style={{
74
+ flex: 1,
75
+ padding: "var(--space-2) var(--space-3)",
76
+ border: "var(--border-w) solid var(--border)",
77
+ borderRadius: "var(--radius-s)",
78
+ background: "var(--surface)",
79
+ color: "var(--ink)",
80
+ font: "inherit",
81
+ }}
82
+ />
83
+ <button
84
+ type="button"
85
+ className="ghost"
86
+ disabled={prompt.trim() === ""}
87
+ onClick={() => out({ reject: { prompt } })}
88
+ >
89
+ 差し戻す
90
+ </button>
91
+ </div>
92
+ </div>
93
+ )
94
+ })
@@ -1 +0,0 @@
1
- export declare const openBrowser: (url: string) => void;
@@ -1,15 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- // ブラウザを開く(開けなければ URL を出して手で開いてもらう。依存は足さない)
3
- export const openBrowser = (url) => {
4
- const [command, args] = process.platform === "darwin"
5
- ? ["open", [url]]
6
- : process.platform === "win32"
7
- ? ["cmd", ["/c", "start", "", url]]
8
- : ["xdg-open", [url]];
9
- try {
10
- spawn(command, args, { stdio: "ignore", detached: true }).unref();
11
- }
12
- catch {
13
- // 何もしない(URL は既に印字済み)
14
- }
15
- };
@@ -1,20 +0,0 @@
1
- import { type ReactNode } from "react";
2
- import type { ApprovalViewContext } from "../human.js";
3
- /**
4
- * 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
5
- * 中身を `flex: 1` にすると、中身が短くても下端に来る。
6
- */
7
- export declare const ApprovalBar: <TOut = unknown>({ ctx, label, disabled, output, value, children, }: {
8
- /** ビューが受け取った ctx */
9
- ctx: ApprovalViewContext<unknown>;
10
- /** 承認ボタンの文言(省略時は「承認して〈次のノード〉へ」) */
11
- label?: string;
12
- /** 承認ボタンを押せない */
13
- disabled?: boolean;
14
- /** 承認したとき approve に流す値(宣言の outputs.approve の形。省略時は value)。`<ApprovalBar<Approve> …>` で型を検査できる */
15
- output?: TOut;
16
- /** 今の値(ビューで直した値。in の形。省略時は ctx.output)。型の合う追加の出口にはこれが流れる */
17
- value?: unknown;
18
- /** バーの左側に足すもの */
19
- children?: ReactNode;
20
- }) => import("react").JSX.Element;
@@ -1,74 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { useApprovalActions } from "../_approvalActions.js";
4
- // 承認バー(承認画面の下の帯)。ビューが自分で置く部品で、内蔵の標準ビューも同じものを使う
5
- // (docs/tasks/wip/承認ビューの顧客repo配置.md の「承認バーもビューが描く」)。
6
- // 材料は ctx.decision、操作は useApprovalActions()。送信中とエラーは decide の結果からここで持つ
7
- const errorMessage = (err) => err instanceof Error ? err.message : "送信に失敗しました";
8
- /**
9
- * 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
10
- * 中身を `flex: 1` にすると、中身が短くても下端に来る。
11
- */
12
- export const ApprovalBar = ({ ctx, label, disabled = false, output, value, children, }) => {
13
- const actions = useApprovalActions();
14
- const [sending, setSending] = useState(false);
15
- const [error, setError] = useState(null);
16
- const { phase, decision } = ctx;
17
- const current = value === undefined ? ctx.output : value;
18
- const approveValue = output === undefined ? current : output;
19
- const send = (request) => {
20
- setSending(true);
21
- setError(null);
22
- request
23
- .catch((err) => setError(errorMessage(err)))
24
- .finally(() => setSending(false));
25
- };
26
- const open = (request) => request.catch((err) => setError(errorMessage(err)));
27
- return (_jsx("div", { style: {
28
- position: "sticky",
29
- bottom: 0,
30
- zIndex: 1,
31
- flex: "none",
32
- display: "flex",
33
- alignItems: "center",
34
- gap: "var(--space-3)",
35
- padding: "var(--space-3) var(--space-6)",
36
- borderTop: phase === "waiting"
37
- ? "var(--border-w-strong) solid var(--human)"
38
- : "var(--border-w) solid var(--border)",
39
- background: "var(--surface)",
40
- }, children: phase === "waiting" ? (_jsxs(_Fragment, { children: [decision.otherAssignees && (_jsxs("span", { style: { fontSize: "var(--text-sm)", color: "var(--muted)" }, children: [_jsx("span", { style: { color: "var(--ink)" }, children: decision.otherAssignees.join("・") }), " さんの担当です — あなたは承認・差し戻しができません"] })), children, _jsx("div", { style: { flex: 1 } }), decision.progress && (_jsxs("span", { style: {
41
- fontFamily: "var(--font-mono)",
42
- fontSize: "var(--text-xs)",
43
- color: "var(--muted)",
44
- whiteSpace: "nowrap",
45
- }, children: [decision.progress.done, "/", decision.progress.total, "\u4EBA \u627F\u8A8D\u6E08\u307F"] })), error && (_jsx("span", { role: "alert", style: { color: "var(--fail)" }, children: error })), decision.exits.map((exit) => (_jsx("button", { type: "button", className: "ghost", disabled: !decision.canDecide || sending, onClick: () => exit.needsInput
46
- ? open(actions.openExitDialog(exit.key, current))
47
- : send(actions.decide(exit.key, current)), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: exit.name }, exit.key))), decision.rejectTargetName && (_jsx("button", { type: "button", className: "ghost", disabled: !decision.canDecide || sending, onClick: () => open(actions.openRejectDialog()), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: "AI\u3078\u5DEE\u3057\u623B\u3059\uFF08reject\uFF09" })), _jsxs("button", { type: "button", className: "primary", disabled: !decision.canDecide || sending || disabled, onClick: () => send(actions.decide("approve", approveValue)), style: {
48
- display: "flex",
49
- alignItems: "center",
50
- gap: "var(--space-2)",
51
- paddingLeft: "var(--space-5)",
52
- paddingRight: "var(--space-5)",
53
- whiteSpace: "nowrap",
54
- }, children: [sending
55
- ? "承認しています…"
56
- : decision.progress?.approvedByMe
57
- ? "承認済み — 全員の承認待ち"
58
- : (label ?? `承認して${decision.approveTargetName ?? "次"}へ`), _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "M4.5 3L12.5 8L4.5 13V3Z", style: { fill: "var(--on-accent)" } }) })] })] })) : phase === "rejected" ? (_jsxs(_Fragment, { children: [_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", style: { flex: "none" }, "aria-hidden": "true", children: [_jsx("rect", { x: "3.5", y: "7", width: "9", height: "6", rx: "1.5", style: { fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 } }), _jsx("path", { d: "M5.5 7V5.5C5.5 4 6.5 3 8 3C9.5 3 10.5 4 10.5 5.5V7", style: { fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 } })] }), _jsx("span", { style: { fontSize: "var(--text-xs)", color: "var(--muted)" }, children: "\u518D\u5B9F\u884C\u304C\u5B8C\u4E86\u3057\u3066\u65B0\u3057\u3044\u4E0B\u66F8\u304D\u304C\u5C4A\u304F\u3068\u3001\u627F\u8A8D\u5F85\u3061\u306B\u623B\u308A\u901A\u77E5\u3055\u308C\u307E\u3059" }), _jsx("div", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "ghost", disabled: true, style: {
59
- fontSize: "var(--text-sm)",
60
- opacity: 0.5,
61
- whiteSpace: "nowrap",
62
- }, children: "\u5DEE\u3057\u623B\u3059\uFF08reject\uFF09" }), _jsx("button", { type: "button", className: "primary", disabled: true, style: { opacity: 0.5, whiteSpace: "nowrap" }, children: "\u627F\u8A8D\u3057\u3066\u6B21\u3078" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { style: {
63
- color: phase === "failed"
64
- ? "var(--fail)"
65
- : phase === "cancelled"
66
- ? "var(--muted)"
67
- : "var(--ok)",
68
- fontSize: "var(--text-sm)",
69
- }, children: phase === "failed" ? "✕" : phase === "cancelled" ? "◼" : "✓" }), _jsx("span", { style: { fontSize: "var(--text-sm)", color: "var(--muted)" }, children: phase === "failed"
70
- ? (decision.failedReason ?? "この step は失敗しました")
71
- : phase === "cancelled"
72
- ? "キャンセル済み — この run は中断されました"
73
- : "対応済み — 承認して次のノードへ渡しました" }), _jsx("div", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "ghost", onClick: () => open(actions.close()), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: "run \u306E\u9032\u884C\u3092\u898B\u308B \u2192" })] })) }));
74
- };
@@ -1,18 +0,0 @@
1
- /**
2
- * ビューから承認画面の操作を頼む口。決定の送信はホストが行う。
3
- * ホストは、この step で・見ている人が決定できる(`ctx.decision.canDecide`)ときだけ受け、
4
- * `decide` はさらに人がビューの中で押した直後のときだけ受ける(開いただけで決定するビューは書けない)。
5
- */
6
- export type ApprovalActions = {
7
- /** 決定する。`port` は `approve` / `reject` / 追加の出口の key。送り終わると承認画面は run の画面へ戻る */
8
- decide: (port: string, value: unknown) => Promise<void>;
9
- /** ホストの差し戻しダイアログを開く(理由と参考ファイルを入れて差し戻す) */
10
- openRejectDialog: () => Promise<void>;
11
- /** 追加の出口に流す値を入れるダイアログを開く(`value` が初期値)。値の型が出口と合わないときに使う */
12
- openExitDialog: (key: string, value: unknown) => Promise<void>;
13
- /** 承認画面を閉じて run の画面へ戻る(右上の × と同じ) */
14
- close: () => Promise<void>;
15
- };
16
- export declare const ApprovalActionsProvider: import("react").Provider<ApprovalActions | null>;
17
- /** 承認画面の操作を頼む口(承認画面の中でだけ使える) */
18
- export declare const useApprovalActions: () => ApprovalActions;
@@ -1,10 +0,0 @@
1
- import { createContext, useContext } from "react";
2
- const ApprovalActionsContext = createContext(null);
3
- export const ApprovalActionsProvider = ApprovalActionsContext.Provider;
4
- /** 承認画面の操作を頼む口(承認画面の中でだけ使える) */
5
- export const useApprovalActions = () => {
6
- const actions = useContext(ApprovalActionsContext);
7
- if (!actions)
8
- throw new Error("承認画面の中で使ってください");
9
- return actions;
10
- };
@@ -1,12 +0,0 @@
1
- // ビューの部品キット(`@mawaru/sdk/views`)。種類(承認・…)に依らない土台:部品・ホストに頼む口。
2
- // mawaru が顧客 repo のビューをまとめるとき、mawaru の今の版のこのキットを同梱する。
3
- // **公開 API**:部品の props とホストに頼む口は後方互換でしか変えない
4
- // (docs/tasks/wip/承認ビューの顧客repo配置.md の「互換の管理」)
5
- export { useViewBridge } from "./_bridge.js";
6
- export { documentCardStyle } from "./_parts/DocumentCard.js";
7
- export { FilePreview } from "./_parts/FilePreview.js";
8
- export { isFileRef } from "./_parts/fileRef.js";
9
- export { isUrl, Linkify, linkify } from "./_parts/Linkify.js";
10
- export { isLongText, Markdown, useAutoGrow } from "./_parts/Markdown.js";
11
- export { asFormSchema, SchemaForm, } from "./_parts/SchemaForm.js";
12
- export { NARROW_WIDTH, useNarrow } from "./_parts/useNarrow.js";