@things-factory/station 10.1.33

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 (185) hide show
  1. package/README.md +124 -0
  2. package/dist-client/index.d.ts +11 -0
  3. package/dist-client/index.js +13 -0
  4. package/dist-client/index.js.map +1 -0
  5. package/dist-client/station/scene/index.d.ts +11 -0
  6. package/dist-client/station/scene/index.js +12 -0
  7. package/dist-client/station/scene/index.js.map +1 -0
  8. package/dist-client/station/scene/station-context-binding.d.ts +25 -0
  9. package/dist-client/station/scene/station-context-binding.js +39 -0
  10. package/dist-client/station/scene/station-context-binding.js.map +1 -0
  11. package/dist-client/station/scene/station-context-scene.d.ts +28 -0
  12. package/dist-client/station/scene/station-context-scene.js +138 -0
  13. package/dist-client/station/scene/station-context-scene.js.map +1 -0
  14. package/dist-client/station/scene/station-counts-scene.d.ts +6 -0
  15. package/dist-client/station/scene/station-counts-scene.js +51 -0
  16. package/dist-client/station/scene/station-counts-scene.js.map +1 -0
  17. package/dist-client/station/scene/station-field-scene.d.ts +6 -0
  18. package/dist-client/station/scene/station-field-scene.js +89 -0
  19. package/dist-client/station/scene/station-field-scene.js.map +1 -0
  20. package/dist-client/station/scene/station-icons.d.ts +82 -0
  21. package/dist-client/station/scene/station-icons.js +131 -0
  22. package/dist-client/station/scene/station-icons.js.map +1 -0
  23. package/dist-client/station/scene/station-numpad-scene.d.ts +9 -0
  24. package/dist-client/station/scene/station-numpad-scene.js +144 -0
  25. package/dist-client/station/scene/station-numpad-scene.js.map +1 -0
  26. package/dist-client/station/scene/station-scene-style.d.ts +51 -0
  27. package/dist-client/station/scene/station-scene-style.js +108 -0
  28. package/dist-client/station/scene/station-scene-style.js.map +1 -0
  29. package/dist-client/station/scene/station-scene.d.ts +295 -0
  30. package/dist-client/station/scene/station-scene.js +68 -0
  31. package/dist-client/station/scene/station-scene.js.map +1 -0
  32. package/dist-client/station/scene/station-who-scene.d.ts +6 -0
  33. package/dist-client/station/scene/station-who-scene.js +48 -0
  34. package/dist-client/station/scene/station-who-scene.js.map +1 -0
  35. package/dist-client/station/scene/station-work-panel-scene.d.ts +28 -0
  36. package/dist-client/station/scene/station-work-panel-scene.js +37 -0
  37. package/dist-client/station/scene/station-work-panel-scene.js.map +1 -0
  38. package/dist-client/station/station-board-binding.d.ts +16 -0
  39. package/dist-client/station/station-board-binding.js +20 -0
  40. package/dist-client/station/station-board-binding.js.map +1 -0
  41. package/dist-client/station/station-context.d.ts +178 -0
  42. package/dist-client/station/station-context.js +30 -0
  43. package/dist-client/station/station-context.js.map +1 -0
  44. package/dist-client/station/station-palette.d.ts +34 -0
  45. package/dist-client/station/station-palette.js +119 -0
  46. package/dist-client/station/station-palette.js.map +1 -0
  47. package/dist-client/station/station-runtime.d.ts +47 -0
  48. package/dist-client/station/station-runtime.js +47 -0
  49. package/dist-client/station/station-runtime.js.map +1 -0
  50. package/dist-client/station/station-scene-context.d.ts +68 -0
  51. package/dist-client/station/station-scene-context.js +62 -0
  52. package/dist-client/station/station-scene-context.js.map +1 -0
  53. package/dist-client/station/station-screen-flow.d.ts +16 -0
  54. package/dist-client/station/station-screen-flow.js +58 -0
  55. package/dist-client/station/station-screen-flow.js.map +1 -0
  56. package/dist-client/station/station-tile-registry.d.ts +89 -0
  57. package/dist-client/station/station-tile-registry.js +86 -0
  58. package/dist-client/station/station-tile-registry.js.map +1 -0
  59. package/dist-client/station/station-work-panel-view.d.ts +23 -0
  60. package/dist-client/station/station-work-panel-view.js +125 -0
  61. package/dist-client/station/station-work-panel-view.js.map +1 -0
  62. package/dist-client/station/station-work-runtime.d.ts +47 -0
  63. package/dist-client/station/station-work-runtime.js +161 -0
  64. package/dist-client/station/station-work-runtime.js.map +1 -0
  65. package/dist-client/station/station-work-scenarios.d.ts +41 -0
  66. package/dist-client/station/station-work-scenarios.js +59 -0
  67. package/dist-client/station/station-work-scenarios.js.map +1 -0
  68. package/dist-client/station/task-counts.d.ts +32 -0
  69. package/dist-client/station/task-counts.js +26 -0
  70. package/dist-client/station/task-counts.js.map +1 -0
  71. package/dist-client/tsconfig.tsbuildinfo +1 -0
  72. package/dist-server/activities/activity-station-screen-control-withdrawal.d.ts +62 -0
  73. package/dist-server/activities/activity-station-screen-control-withdrawal.js +147 -0
  74. package/dist-server/activities/activity-station-screen-control-withdrawal.js.map +1 -0
  75. package/dist-server/activities/activity-station-screen-release.d.ts +81 -0
  76. package/dist-server/activities/activity-station-screen-release.js +147 -0
  77. package/dist-server/activities/activity-station-screen-release.js.map +1 -0
  78. package/dist-server/activities/index.d.ts +49 -0
  79. package/dist-server/activities/index.js +21 -0
  80. package/dist-server/activities/index.js.map +1 -0
  81. package/dist-server/index.d.ts +3 -0
  82. package/dist-server/index.js +8 -0
  83. package/dist-server/index.js.map +1 -0
  84. package/dist-server/service/index.d.ts +23 -0
  85. package/dist-server/service/index.js +41 -0
  86. package/dist-server/service/index.js.map +1 -0
  87. package/dist-server/service/station/deletion-changes.d.ts +14 -0
  88. package/dist-server/service/station/deletion-changes.js +25 -0
  89. package/dist-server/service/station/deletion-changes.js.map +1 -0
  90. package/dist-server/service/station/index.d.ts +13 -0
  91. package/dist-server/service/station/index.js +25 -0
  92. package/dist-server/service/station/index.js.map +1 -0
  93. package/dist-server/service/station/reversibility.d.ts +21 -0
  94. package/dist-server/service/station/reversibility.js +24 -0
  95. package/dist-server/service/station/reversibility.js.map +1 -0
  96. package/dist-server/service/station/screen-model-digest.d.ts +60 -0
  97. package/dist-server/service/station/screen-model-digest.js +97 -0
  98. package/dist-server/service/station/screen-model-digest.js.map +1 -0
  99. package/dist-server/service/station/staged-material.d.ts +62 -0
  100. package/dist-server/service/station/staged-material.js +44 -0
  101. package/dist-server/service/station/staged-material.js.map +1 -0
  102. package/dist-server/service/station/station-argument-sources.d.ts +31 -0
  103. package/dist-server/service/station/station-argument-sources.js +66 -0
  104. package/dist-server/service/station/station-argument-sources.js.map +1 -0
  105. package/dist-server/service/station/station-assignment-choice.d.ts +81 -0
  106. package/dist-server/service/station/station-assignment-choice.js +105 -0
  107. package/dist-server/service/station/station-assignment-choice.js.map +1 -0
  108. package/dist-server/service/station/station-assignment-resolver.d.ts +104 -0
  109. package/dist-server/service/station/station-assignment-resolver.js +426 -0
  110. package/dist-server/service/station/station-assignment-resolver.js.map +1 -0
  111. package/dist-server/service/station/station-assignment-service.d.ts +33 -0
  112. package/dist-server/service/station/station-assignment-service.js +64 -0
  113. package/dist-server/service/station/station-assignment-service.js.map +1 -0
  114. package/dist-server/service/station/station-axes.d.ts +9 -0
  115. package/dist-server/service/station/station-axes.js +16 -0
  116. package/dist-server/service/station/station-axes.js.map +1 -0
  117. package/dist-server/service/station/station-flow.d.ts +156 -0
  118. package/dist-server/service/station/station-flow.js +255 -0
  119. package/dist-server/service/station/station-flow.js.map +1 -0
  120. package/dist-server/service/station/station-generate.d.ts +76 -0
  121. package/dist-server/service/station/station-generate.js +178 -0
  122. package/dist-server/service/station/station-generate.js.map +1 -0
  123. package/dist-server/service/station/station-product.d.ts +123 -0
  124. package/dist-server/service/station/station-product.js +77 -0
  125. package/dist-server/service/station/station-product.js.map +1 -0
  126. package/dist-server/service/station/station-runtime-binding.d.ts +6 -0
  127. package/dist-server/service/station/station-runtime-binding.js +46 -0
  128. package/dist-server/service/station/station-runtime-binding.js.map +1 -0
  129. package/dist-server/service/station/station-screen-assignment.d.ts +94 -0
  130. package/dist-server/service/station/station-screen-assignment.js +154 -0
  131. package/dist-server/service/station/station-screen-assignment.js.map +1 -0
  132. package/dist-server/service/station/station-screen-control-withdrawal-resolver.d.ts +37 -0
  133. package/dist-server/service/station/station-screen-control-withdrawal-resolver.js +130 -0
  134. package/dist-server/service/station/station-screen-control-withdrawal-resolver.js.map +1 -0
  135. package/dist-server/service/station/station-screen-control-withdrawal.d.ts +104 -0
  136. package/dist-server/service/station/station-screen-control-withdrawal.js +128 -0
  137. package/dist-server/service/station/station-screen-control-withdrawal.js.map +1 -0
  138. package/dist-server/service/station/station-screen-draft-resolver.d.ts +47 -0
  139. package/dist-server/service/station/station-screen-draft-resolver.js +153 -0
  140. package/dist-server/service/station/station-screen-draft-resolver.js.map +1 -0
  141. package/dist-server/service/station/station-screen-layout.d.ts +119 -0
  142. package/dist-server/service/station/station-screen-layout.js +202 -0
  143. package/dist-server/service/station/station-screen-layout.js.map +1 -0
  144. package/dist-server/service/station/station-screen-list-resolver.d.ts +69 -0
  145. package/dist-server/service/station/station-screen-list-resolver.js +215 -0
  146. package/dist-server/service/station/station-screen-list-resolver.js.map +1 -0
  147. package/dist-server/service/station/station-screen-publish-subscriber.d.ts +22 -0
  148. package/dist-server/service/station/station-screen-publish-subscriber.js +90 -0
  149. package/dist-server/service/station/station-screen-publish-subscriber.js.map +1 -0
  150. package/dist-server/service/station/station-screen-release-gate.d.ts +66 -0
  151. package/dist-server/service/station/station-screen-release-gate.js +67 -0
  152. package/dist-server/service/station/station-screen-release-gate.js.map +1 -0
  153. package/dist-server/service/station/station-screen-release-record.d.ts +62 -0
  154. package/dist-server/service/station/station-screen-release-record.js +139 -0
  155. package/dist-server/service/station/station-screen-release-record.js.map +1 -0
  156. package/dist-server/service/station/station-screen-release-signature.d.ts +23 -0
  157. package/dist-server/service/station/station-screen-release-signature.js +32 -0
  158. package/dist-server/service/station/station-screen-release-signature.js.map +1 -0
  159. package/dist-server/service/station/station-screen-scorecard.d.ts +15 -0
  160. package/dist-server/service/station/station-screen-scorecard.js +35 -0
  161. package/dist-server/service/station/station-screen-scorecard.js.map +1 -0
  162. package/dist-server/service/station/station-screen-spec-resolver.d.ts +36 -0
  163. package/dist-server/service/station/station-screen-spec-resolver.js +202 -0
  164. package/dist-server/service/station/station-screen-spec-resolver.js.map +1 -0
  165. package/dist-server/service/station/station-screen-spec.d.ts +104 -0
  166. package/dist-server/service/station/station-screen-spec.js +114 -0
  167. package/dist-server/service/station/station-screen-spec.js.map +1 -0
  168. package/dist-server/service/station/station-screen-thumbnail.d.ts +11 -0
  169. package/dist-server/service/station/station-screen-thumbnail.js +36 -0
  170. package/dist-server/service/station/station-screen-thumbnail.js.map +1 -0
  171. package/dist-server/service/station/station-service-resolver.d.ts +27 -0
  172. package/dist-server/service/station/station-service-resolver.js +95 -0
  173. package/dist-server/service/station/station-service-resolver.js.map +1 -0
  174. package/dist-server/tsconfig.tsbuildinfo +1 -0
  175. package/package.json +44 -0
  176. package/shared/station-axes.d.ts +4 -0
  177. package/shared/station-axes.js +155 -0
  178. package/shared/station-screen-flow-check.d.ts +12 -0
  179. package/shared/station-screen-flow-check.js +74 -0
  180. package/things-scene.config.js +15 -0
  181. package/translations/en.json +42 -0
  182. package/translations/ja.json +42 -0
  183. package/translations/ko.json +42 -0
  184. package/translations/ms.json +42 -0
  185. package/translations/zh.json +42 -0
@@ -0,0 +1,178 @@
1
+ import type { TaskCounts } from './task-counts';
2
+ /**
3
+ * 현장 화면의 공통 컨텍스트 — **용어만 있는 파일. 아무것도 부르지 않는다.**
4
+ *
5
+ * ── 왜 순수한가 ────────────────────────────────────────────────────────────
6
+ * 이 용어를 읽는 곳이 셋이다: Lit 화면 · 보드의 씬 컴포넌트 · 고객이 덧붙이는 조각. 그중 씬 컴포넌트는
7
+ * `@things-factory/*` 를 참조할 수 없다(씬 라이브러리로 옮겨 갈 수 있어야 한다). 그래서 **용어는 아무
8
+ * 것도 부르지 않는 파일에 두고**, GraphQL 을 아는 쪽은 따로 둔다.
9
+ *
10
+ * ── 없는 것은 없다고 말한다 ────────────────────────────────────────────────
11
+ * 비어 있는 자리를 0 이나 빈 문자열로 채우지 않는다. 「불량 0」과 「불량을 세지 않았다」는 다른 사실이고,
12
+ * 화면이 그 둘을 같은 모양으로 그리면 사람이 잘못 읽는다.
13
+ */
14
+ export interface StationCounts {
15
+ good: number;
16
+ scrap: number;
17
+ tasks: number;
18
+ }
19
+ export interface StationPerson {
20
+ /** 사번 — 이 시스템에서 사람을 가리키는 이름. */
21
+ name: string;
22
+ /** 스캔한 시각. 교대가 바뀌었는데 앞 사람이 남아 있는 것을 화면이 알아채는 근거다. */
23
+ scannedAt: number;
24
+ }
25
+ export interface StationSnapshot<Ground = unknown> {
26
+ equipmentName: string;
27
+ /** 이 작업대가 하는 공정. 주소가 정한다 — 실적에 없어서는 안 되는 값이다. */
28
+ operationKey?: string;
29
+ /** 스캔해 들어온 사람. 없으면 아직 아무도 오지 않았다. */
30
+ operator?: StationPerson;
31
+ /**
32
+ * 지금 적고 있는 작업의 이름.
33
+ *
34
+ * 착수할 때 정하고 끝낼 때까지 같은 이름을 씁니다. **같은 이름이면 갱신**이라, 수량을 여러 번
35
+ * 올려도 작업은 하나입니다.
36
+ */
37
+ taskName?: string;
38
+ /** 멈춘 시각. 멈춰 있는 동안만 값이 있다 — 다시 돌릴 때 그 구간을 적는다. */
39
+ pausedAt?: number;
40
+ /** 멈춘 이유. 다시 돌릴 때 구간과 함께 적는다. */
41
+ pausedReason?: string;
42
+ /**
43
+ * 고른 사유가 어느 상태인가. 다시 돌릴 때 이 값으로 구간이 앉는다.
44
+ *
45
+ * 사유와 따로 두는 이유는 **사유는 안 골라도 멈출 수 있기** 때문이다. 그때는 둘 다 비어 있고
46
+ * 구간은 `down` 으로 앉는다.
47
+ */
48
+ pausedStatus?: string;
49
+ /** 「기타」처럼 이름만으로는 알 수 없는 사유가 요구하는 한 줄. */
50
+ pausedDetail?: string;
51
+ /** 열려 있는 작업 — 이것이 있으면 이미 착수한 것이다. */
52
+ runningTaskName?: string;
53
+ runningSince?: number;
54
+ today: StationCounts;
55
+ /** What the task on this screen has recorded. Counts are sent from this, never from `today`. */
56
+ task: TaskCounts;
57
+ /**
58
+ * 지금 와 있는 사람의 등급들 — 표준 `PersonnelClassID`.
59
+ *
60
+ * **빈 배열과 없는 것은 다르다.** 빈 배열은 「등급이 하나도 없는 사람」이고, 없는 것은 「누군지 모른다」
61
+ * 다. 자격으로 무엇을 막는 화면이 둘을 같게 다루면 미등록 사번이 전부 통과한다.
62
+ */
63
+ operatorPersonnelClassIds?: string[];
64
+ /**
65
+ * 창의 처음과 끝. **서버가 교대로 잡는다** — 읽기 전에는 없다.
66
+ *
67
+ * 「오늘」이 어디서 끊기는지는 공장이 교대로 정한다(`@things-factory/work-shift`). 화면이 자정부터
68
+ * 24시간으로 잡으면 야간 교대의 실적이 이틀로 갈린다.
69
+ */
70
+ windowFrom?: number;
71
+ windowTo?: number;
72
+ /** 지금이 어느 교대인가. 교대 안이 아니면 없다. */
73
+ workShift?: string;
74
+ /** 이 창이 속한 조업일. */
75
+ workDate?: string;
76
+ /**
77
+ * 창을 무엇을 근거로 잡았나 — 화면이 그대로 말한다.
78
+ *
79
+ * shift 지금이 어느 교대 안이다
80
+ * between-shifts 교대는 있는데 지금은 그 사이다
81
+ * no-shifts-declared 교대가 하나도 안 정해져 있다
82
+ * given 부르는 쪽이 창을 줬다
83
+ * unknown 아직 안 읽었다
84
+ *
85
+ * 앞의 셋을 한 모양으로 뭉개면 「교대를 안 정했다」와 「지금은 조업 시간이 아니다」가 같아 보인다.
86
+ */
87
+ windowBasis: string;
88
+ /**
89
+ * 태블릿 시계와 서버 시계의 차. 태블릿 시계는 곧잘 틀린다 — 화면이 「10분 전」을 말할 때 이 값을
90
+ * 빼고 말해야 맞다.
91
+ */
92
+ clockSkewMs: number;
93
+ /** 읽는 중인가. 조각이 회전판을 그릴지 값을 그릴지 가르는 하나의 근거다. */
94
+ loading: boolean;
95
+ /**
96
+ * 서버에서 마지막으로 읽은 시각. 아직 한 번도 안 읽었으면 없다.
97
+ *
98
+ * **갱신이 멈춘 화면과 아무 일도 없는 화면은 똑같이 보인다.** 숫자가 30분째 그대로일 때 작업자가
99
+ * 「한가하구나」와 「이 화면 죽었나」를 가를 근거가 이것뿐이다.
100
+ */
101
+ readAt?: number;
102
+ /**
103
+ * 스스로 다시 읽고 있나.
104
+ *
105
+ * 껐으면 이 화면의 숫자는 **읽은 그 순간의 것**이고 그 뒤로는 모른다. 화면이 그렇게 말해야 한다.
106
+ */
107
+ live: boolean;
108
+ /**
109
+ * 무엇이 잘못됐나 — **사용자가 읽을 문장.** 없으면 잘못된 것이 없다.
110
+ *
111
+ * 비어 있는 화면으로 두지 않는다. 못 읽은 것과 읽을 것이 없는 것은 다른 사실이다.
112
+ */
113
+ problem?: string;
114
+ /** 이 제품의 바탕. 기계는 들고만 다닌다. */
115
+ ground: Ground;
116
+ }
117
+ /** 이 제품이 쓰는 판. 화면과 런타임은 이것으로 읽는다. */
118
+ export type StationAction = {
119
+ kind: 'start';
120
+ } | {
121
+ kind: 'count';
122
+ good?: number;
123
+ scrap?: number;
124
+ } | {
125
+ kind: 'pause';
126
+ reasonCode?: string;
127
+ reasonDetail?: string;
128
+ reasonStatus?: string;
129
+ }
130
+ /**
131
+ * 잘못 스캔한 자재를 뺀다.
132
+ *
133
+ * 뺄 수 없으면 현장은 스캔을 그만두고 손으로 적는다. 그러면 계보가 통째로 사라진다.
134
+ */
135
+ | {
136
+ kind: 'unstage';
137
+ at: number;
138
+ } | {
139
+ kind: 'resume';
140
+ } | {
141
+ kind: 'finish';
142
+ };
143
+ /**
144
+ * 스캔한 것이 무엇인가 — **고객마다 다른 자리.**
145
+ *
146
+ * 사번에 접두사를 붙이는 공장이 있고, 자리수로 가르는 공장이 있고, 작업지시 라벨에 회사 코드가 앞에
147
+ * 붙는 공장이 있다. 그것을 화면 코드에 적으면 고객마다 화면을 새로 만들게 된다. 규칙을 값으로 두고
148
+ * 화면은 규칙을 읽기만 한다.
149
+ */
150
+ export interface StationScanRule {
151
+ kind: 'person' | 'order' | 'lot' | 'asset';
152
+ /** 이 정규식에 걸리면 이 종류다. */
153
+ match: RegExp;
154
+ /** 값에서 이름만 뽑는다. 없으면 스캔한 값 그대로가 이름이다. */
155
+ extract?: (scanned: string) => string;
156
+ }
157
+ /**
158
+ * 어느 쪽인지 모르는 값은 **받지 않는다.**
159
+ *
160
+ * 짐작으로 넣으면 엉뚱한 사람 이름으로 실적이 남고, 그 실적은 나중에 아무도 못 고친다.
161
+ */
162
+ export declare function classifyScan(value: string, rules: StationScanRule[]): {
163
+ kind: StationScanRule['kind'];
164
+ name: string;
165
+ } | undefined;
166
+ /**
167
+ * 아무도 규칙을 주지 않았을 때. 사번은 접두사로 **가르고**, 나머지는 작업지시로 본다.
168
+ *
169
+ * ── 기본값은 찍은 값을 바꾸지 않는다 ───────────────────────────────────────
170
+ * 전에는 `P-` 를 떼어냈다(`extract: value => value.slice(2)`). 그런데 접두사가 **사번의 일부인** 공장이
171
+ * 있다 — 이 제품의 시드가 그렇다(`Person.name = 'P-CAM-A-0001'`). 떼어내면 사번이 `CAM-A-0001` 이 되어
172
+ * 인원 마스터에서 사람을 못 찾고, 자격으로 갈리는 화면 배정이 조용히 안 걸린다. 오류는 나지 않는다 —
173
+ * 그냥 그 사람이 등급 없는 사람이 된다.
174
+ *
175
+ * 가르는 것과 **고치는 것**은 다른 일이다. 접두사로 종류를 가르는 것은 기본값으로 둘 만하고, 값에서
176
+ * 무엇을 떼어내는 것은 그 공장이 그렇게 쓴다고 말했을 때만 한다 — `extract` 는 그 자리에 그대로 있다.
177
+ */
178
+ export declare const DEFAULT_SCAN_RULES: StationScanRule[];
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 어느 쪽인지 모르는 값은 **받지 않는다.**
3
+ *
4
+ * 짐작으로 넣으면 엉뚱한 사람 이름으로 실적이 남고, 그 실적은 나중에 아무도 못 고친다.
5
+ */
6
+ export function classifyScan(value, rules) {
7
+ for (const rule of rules) {
8
+ if (rule.match.test(value)) {
9
+ return { kind: rule.kind, name: rule.extract ? rule.extract(value) : value };
10
+ }
11
+ }
12
+ return undefined;
13
+ }
14
+ /**
15
+ * 아무도 규칙을 주지 않았을 때. 사번은 접두사로 **가르고**, 나머지는 작업지시로 본다.
16
+ *
17
+ * ── 기본값은 찍은 값을 바꾸지 않는다 ───────────────────────────────────────
18
+ * 전에는 `P-` 를 떼어냈다(`extract: value => value.slice(2)`). 그런데 접두사가 **사번의 일부인** 공장이
19
+ * 있다 — 이 제품의 시드가 그렇다(`Person.name = 'P-CAM-A-0001'`). 떼어내면 사번이 `CAM-A-0001` 이 되어
20
+ * 인원 마스터에서 사람을 못 찾고, 자격으로 갈리는 화면 배정이 조용히 안 걸린다. 오류는 나지 않는다 —
21
+ * 그냥 그 사람이 등급 없는 사람이 된다.
22
+ *
23
+ * 가르는 것과 **고치는 것**은 다른 일이다. 접두사로 종류를 가르는 것은 기본값으로 둘 만하고, 값에서
24
+ * 무엇을 떼어내는 것은 그 공장이 그렇게 쓴다고 말했을 때만 한다 — `extract` 는 그 자리에 그대로 있다.
25
+ */
26
+ export const DEFAULT_SCAN_RULES = [
27
+ { kind: 'person', match: /^P-/ },
28
+ { kind: 'order', match: /./ }
29
+ ];
30
+ //# sourceMappingURL=station-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-context.js","sourceRoot":"","sources":["../../client/station/station-context.ts"],"names":[],"mappings":"AA0KA;;;;GAIG;AAEH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,KAAwB;IAClE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;QAC9E,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE;IAChC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE;CAC9B,CAAA","sourcesContent":["import type { TaskCounts } from './task-counts'\n\n/**\n * 현장 화면의 공통 컨텍스트 — **용어만 있는 파일. 아무것도 부르지 않는다.**\n *\n * ── 왜 순수한가 ────────────────────────────────────────────────────────────\n * 이 용어를 읽는 곳이 셋이다: Lit 화면 · 보드의 씬 컴포넌트 · 고객이 덧붙이는 조각. 그중 씬 컴포넌트는\n * `@things-factory/*` 를 참조할 수 없다(씬 라이브러리로 옮겨 갈 수 있어야 한다). 그래서 **용어는 아무\n * 것도 부르지 않는 파일에 두고**, GraphQL 을 아는 쪽은 따로 둔다.\n *\n * ── 없는 것은 없다고 말한다 ────────────────────────────────────────────────\n * 비어 있는 자리를 0 이나 빈 문자열로 채우지 않는다. 「불량 0」과 「불량을 세지 않았다」는 다른 사실이고,\n * 화면이 그 둘을 같은 모양으로 그리면 사람이 잘못 읽는다.\n */\n\nexport interface StationCounts {\n good: number\n scrap: number\n tasks: number\n}\n\nexport interface StationPerson {\n /** 사번 — 이 시스템에서 사람을 가리키는 이름. */\n name: string\n /** 스캔한 시각. 교대가 바뀌었는데 앞 사람이 남아 있는 것을 화면이 알아채는 근거다. */\n scannedAt: number\n}\n\nexport interface StationSnapshot<Ground = unknown> {\n equipmentName: string\n\n /** 이 작업대가 하는 공정. 주소가 정한다 — 실적에 없어서는 안 되는 값이다. */\n operationKey?: string\n\n /** 스캔해 들어온 사람. 없으면 아직 아무도 오지 않았다. */\n operator?: StationPerson\n\n /**\n * 지금 적고 있는 작업의 이름.\n *\n * 착수할 때 정하고 끝낼 때까지 같은 이름을 씁니다. **같은 이름이면 갱신**이라, 수량을 여러 번\n * 올려도 작업은 하나입니다.\n */\n taskName?: string\n\n /** 멈춘 시각. 멈춰 있는 동안만 값이 있다 — 다시 돌릴 때 그 구간을 적는다. */\n pausedAt?: number\n\n /** 멈춘 이유. 다시 돌릴 때 구간과 함께 적는다. */\n pausedReason?: string\n /**\n * 고른 사유가 어느 상태인가. 다시 돌릴 때 이 값으로 구간이 앉는다.\n *\n * 사유와 따로 두는 이유는 **사유는 안 골라도 멈출 수 있기** 때문이다. 그때는 둘 다 비어 있고\n * 구간은 `down` 으로 앉는다.\n */\n pausedStatus?: string\n /** 「기타」처럼 이름만으로는 알 수 없는 사유가 요구하는 한 줄. */\n pausedDetail?: string\n\n /** 열려 있는 작업 — 이것이 있으면 이미 착수한 것이다. */\n runningTaskName?: string\n runningSince?: number\n\n today: StationCounts\n /** What the task on this screen has recorded. Counts are sent from this, never from `today`. */\n task: TaskCounts\n\n /**\n * 지금 와 있는 사람의 등급들 — 표준 `PersonnelClassID`.\n *\n * **빈 배열과 없는 것은 다르다.** 빈 배열은 「등급이 하나도 없는 사람」이고, 없는 것은 「누군지 모른다」\n * 다. 자격으로 무엇을 막는 화면이 둘을 같게 다루면 미등록 사번이 전부 통과한다.\n */\n operatorPersonnelClassIds?: string[]\n\n /**\n * 창의 처음과 끝. **서버가 교대로 잡는다** — 읽기 전에는 없다.\n *\n * 「오늘」이 어디서 끊기는지는 공장이 교대로 정한다(`@things-factory/work-shift`). 화면이 자정부터\n * 24시간으로 잡으면 야간 교대의 실적이 이틀로 갈린다.\n */\n windowFrom?: number\n windowTo?: number\n\n /** 지금이 어느 교대인가. 교대 안이 아니면 없다. */\n workShift?: string\n\n /** 이 창이 속한 조업일. */\n workDate?: string\n\n /**\n * 창을 무엇을 근거로 잡았나 — 화면이 그대로 말한다.\n *\n * shift 지금이 어느 교대 안이다\n * between-shifts 교대는 있는데 지금은 그 사이다\n * no-shifts-declared 교대가 하나도 안 정해져 있다\n * given 부르는 쪽이 창을 줬다\n * unknown 아직 안 읽었다\n *\n * 앞의 셋을 한 모양으로 뭉개면 「교대를 안 정했다」와 「지금은 조업 시간이 아니다」가 같아 보인다.\n */\n windowBasis: string\n\n /**\n * 태블릿 시계와 서버 시계의 차. 태블릿 시계는 곧잘 틀린다 — 화면이 「10분 전」을 말할 때 이 값을\n * 빼고 말해야 맞다.\n */\n clockSkewMs: number\n\n /** 읽는 중인가. 조각이 회전판을 그릴지 값을 그릴지 가르는 하나의 근거다. */\n loading: boolean\n\n /**\n * 서버에서 마지막으로 읽은 시각. 아직 한 번도 안 읽었으면 없다.\n *\n * **갱신이 멈춘 화면과 아무 일도 없는 화면은 똑같이 보인다.** 숫자가 30분째 그대로일 때 작업자가\n * 「한가하구나」와 「이 화면 죽었나」를 가를 근거가 이것뿐이다.\n */\n readAt?: number\n\n /**\n * 스스로 다시 읽고 있나.\n *\n * 껐으면 이 화면의 숫자는 **읽은 그 순간의 것**이고 그 뒤로는 모른다. 화면이 그렇게 말해야 한다.\n */\n live: boolean\n\n /**\n * 무엇이 잘못됐나 — **사용자가 읽을 문장.** 없으면 잘못된 것이 없다.\n *\n * 비어 있는 화면으로 두지 않는다. 못 읽은 것과 읽을 것이 없는 것은 다른 사실이다.\n */\n problem?: string\n\n /** 이 제품의 바탕. 기계는 들고만 다닌다. */\n ground: Ground\n}\n\n/** 이 제품이 쓰는 판. 화면과 런타임은 이것으로 읽는다. */\n\nexport type StationAction =\n | { kind: 'start' }\n | { kind: 'count'; good?: number; scrap?: number }\n | { kind: 'pause'; reasonCode?: string; reasonDetail?: string; reasonStatus?: string }\n /**\n * 잘못 스캔한 자재를 뺀다.\n *\n * 뺄 수 없으면 현장은 스캔을 그만두고 손으로 적는다. 그러면 계보가 통째로 사라진다.\n */\n | { kind: 'unstage'; at: number }\n | { kind: 'resume' }\n | { kind: 'finish' }\n\n/**\n * 스캔한 것이 무엇인가 — **고객마다 다른 자리.**\n *\n * 사번에 접두사를 붙이는 공장이 있고, 자리수로 가르는 공장이 있고, 작업지시 라벨에 회사 코드가 앞에\n * 붙는 공장이 있다. 그것을 화면 코드에 적으면 고객마다 화면을 새로 만들게 된다. 규칙을 값으로 두고\n * 화면은 규칙을 읽기만 한다.\n */\n\nexport interface StationScanRule {\n kind: 'person' | 'order' | 'lot' | 'asset'\n /** 이 정규식에 걸리면 이 종류다. */\n match: RegExp\n /** 값에서 이름만 뽑는다. 없으면 스캔한 값 그대로가 이름이다. */\n extract?: (scanned: string) => string\n}\n\n/**\n * 어느 쪽인지 모르는 값은 **받지 않는다.**\n *\n * 짐작으로 넣으면 엉뚱한 사람 이름으로 실적이 남고, 그 실적은 나중에 아무도 못 고친다.\n */\n\nexport function classifyScan(value: string, rules: StationScanRule[]): { kind: StationScanRule['kind']; name: string } | undefined {\n for (const rule of rules) {\n if (rule.match.test(value)) {\n return { kind: rule.kind, name: rule.extract ? rule.extract(value) : value }\n }\n }\n\n return undefined\n}\n\n/**\n * 아무도 규칙을 주지 않았을 때. 사번은 접두사로 **가르고**, 나머지는 작업지시로 본다.\n *\n * ── 기본값은 찍은 값을 바꾸지 않는다 ───────────────────────────────────────\n * 전에는 `P-` 를 떼어냈다(`extract: value => value.slice(2)`). 그런데 접두사가 **사번의 일부인** 공장이\n * 있다 — 이 제품의 시드가 그렇다(`Person.name = 'P-CAM-A-0001'`). 떼어내면 사번이 `CAM-A-0001` 이 되어\n * 인원 마스터에서 사람을 못 찾고, 자격으로 갈리는 화면 배정이 조용히 안 걸린다. 오류는 나지 않는다 —\n * 그냥 그 사람이 등급 없는 사람이 된다.\n *\n * 가르는 것과 **고치는 것**은 다른 일이다. 접두사로 종류를 가르는 것은 기본값으로 둘 만하고, 값에서\n * 무엇을 떼어내는 것은 그 공장이 그렇게 쓴다고 말했을 때만 한다 — `extract` 는 그 자리에 그대로 있다.\n */\n\nexport const DEFAULT_SCAN_RULES: StationScanRule[] = [\n { kind: 'person', match: /^P-/ },\n { kind: 'order', match: /./ }\n]\n"]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 저작면 팔레트에 서는 **기계의 타일 다섯**과 그 그룹 (ADR-0083 결정 6).
3
+ *
4
+ * ── 어떻게 서나 ─────────────────────────────────────────────────────────────
5
+ * 이 꾸러미의 `package.json` 에 `"things-scene": true` 가 있고 뿌리에 `things-scene.config.js` 가
6
+ * 있으면, `@things-factory/env` 가 의존 꾸러미를 훑어 이것을 모으고 보드 모델러가 세운다. 꾸러미
7
+ * 42 개가 이미 그 길이다.
8
+ *
9
+ * operato-plant 는 손으로 등록했었다 — 그 부품이 **앱 안에 있어서** 그물에 안 걸렸기 때문이다. 옮기고
10
+ * 나면 그 까닭이 없어진다. 플랫폼에 길이 있는데 옆에 사설 수집기를 세우지 않는다.
11
+ *
12
+ * ── 제품의 타일은 여기 없다 ─────────────────────────────────────────────────
13
+ * 작업지시 · 정비 · 자재 · 지시서 · 동작은 구조를 읽으므로 제품의 것이고, 제품이
14
+ * `registerStationTiles` 로 건다. 가르는 잣대는 **구조를 읽는가**다.
15
+ *
16
+ * ── 이름표는 열쇠다 ─────────────────────────────────────────────────────────
17
+ * `labelKey` 를 항목이 들고 온다(UX 판정 2026-09-20). 값으로 굳히면 언어를 바꿨을 때 옛말이 남는다 —
18
+ * 등록은 기동 때 한 번이고 언어는 그 뒤에도 바뀐다. 손으로 적은 둘째 지도를 두지 않으려는 것이기도
19
+ * 하다: 지도가 안 갈리면 놓인 타일이 이름 대신 `station-counts` 로 뜬다.
20
+ */
21
+ import type { StationTileEntry } from './station-tile-registry.js';
22
+ export declare const stationGroup: {
23
+ name: string;
24
+ description: string;
25
+ icon: string;
26
+ templates: never[];
27
+ };
28
+ /**
29
+ * 기본 크기는 **작업대에서 읽히는 크기**로 잡는다. 저작자가 놓자마자 줄여야 한다면 그 기본값이 틀린
30
+ * 것이다 — 현장 화면은 한 걸음 떨어져서 읽는다.
31
+ *
32
+ * `equipment` 를 비워 둔다. 비면 그 화면이 걸린 작업대를 따르고, 적으면 그 설비에 못 박힌다.
33
+ */
34
+ export declare const stationCommonTiles: StationTileEntry[];
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 저작면 팔레트에 서는 **기계의 타일 다섯**과 그 그룹 (ADR-0083 결정 6).
3
+ *
4
+ * ── 어떻게 서나 ─────────────────────────────────────────────────────────────
5
+ * 이 꾸러미의 `package.json` 에 `"things-scene": true` 가 있고 뿌리에 `things-scene.config.js` 가
6
+ * 있으면, `@things-factory/env` 가 의존 꾸러미를 훑어 이것을 모으고 보드 모델러가 세운다. 꾸러미
7
+ * 42 개가 이미 그 길이다.
8
+ *
9
+ * operato-plant 는 손으로 등록했었다 — 그 부품이 **앱 안에 있어서** 그물에 안 걸렸기 때문이다. 옮기고
10
+ * 나면 그 까닭이 없어진다. 플랫폼에 길이 있는데 옆에 사설 수집기를 세우지 않는다.
11
+ *
12
+ * ── 제품의 타일은 여기 없다 ─────────────────────────────────────────────────
13
+ * 작업지시 · 정비 · 자재 · 지시서 · 동작은 구조를 읽으므로 제품의 것이고, 제품이
14
+ * `registerStationTiles` 로 건다. 가르는 잣대는 **구조를 읽는가**다.
15
+ *
16
+ * ── 이름표는 열쇠다 ─────────────────────────────────────────────────────────
17
+ * `labelKey` 를 항목이 들고 온다(UX 판정 2026-09-20). 값으로 굳히면 언어를 바꿨을 때 옛말이 남는다 —
18
+ * 등록은 기동 때 한 번이고 언어는 그 뒤에도 바뀐다. 손으로 적은 둘째 지도를 두지 않으려는 것이기도
19
+ * 하다: 지도가 안 갈리면 놓인 타일이 이름 대신 `station-counts` 로 뜬다.
20
+ */
21
+ import { ICON_CONTEXT, ICON_COUNTS, ICON_FIELD, ICON_NUMPAD, ICON_WHO } from './scene/station-icons.js';
22
+ /*
23
+ * 그룹 아이콘 — 작업대 위의 태블릿.
24
+ *
25
+ * 다른 그룹 아이콘과 같은 붓을 쓴다: 주된 것은 굵게(2.5), 윤곽은 가늘게(1.0), 강조는 채운다.
26
+ * `{{strokeColor}}` 는 모델러가 테마 색으로 바꿔 넣는다.
27
+ */
28
+ const icon = `
29
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
30
+ <style type="text/css">
31
+ .st0{fill:{{strokeColor}};}
32
+ .st14{fill:none;stroke:{{strokeColor}};stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
33
+ .st15{fill:none;stroke:{{strokeColor}};stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
34
+ </style>
35
+ <g>
36
+ <!-- 화면 — 이 그룹의 주인공이라 굵게 -->
37
+ <rect class="st14" x="6" y="4.5" width="18" height="12" rx="1.5"/>
38
+ <!-- 화면 안의 한 줄. 작업자가 읽는 값이 있다는 뜻 -->
39
+ <line class="st15" x1="9" y1="8.5" x2="17" y2="8.5"/>
40
+ <line class="st15" x1="9" y1="12" x2="21" y2="12"/>
41
+ <!-- 받침대 -->
42
+ <line class="st15" x1="15" y1="16.5" x2="15" y2="20"/>
43
+ <!-- 작업대 상판 -->
44
+ <rect class="st0" x="4" y="20" width="22" height="2"/>
45
+ <!-- 다리 -->
46
+ <line class="st15" x1="7" y1="22" x2="7" y2="26.5"/>
47
+ <line class="st15" x1="23" y1="22" x2="23" y2="26.5"/>
48
+ </g>
49
+ </svg>
50
+ `;
51
+ export const stationGroup = {
52
+ name: 'station',
53
+ description: 'what an operator sees at a workbench — the values, the prompts, and the buttons that record work',
54
+ icon,
55
+ templates: []
56
+ };
57
+ /** 놓았을 때의 자리와 크기. 겹치지 않게 조금씩 어긋나 떨어진다. */
58
+ const at = (left, top, width, height) => ({ left, top, width, height });
59
+ /**
60
+ * 기본 크기는 **작업대에서 읽히는 크기**로 잡는다. 저작자가 놓자마자 줄여야 한다면 그 기본값이 틀린
61
+ * 것이다 — 현장 화면은 한 걸음 떨어져서 읽는다.
62
+ *
63
+ * `equipment` 를 비워 둔다. 비면 그 화면이 걸린 작업대를 따르고, 적으면 그 설비에 못 박힌다.
64
+ */
65
+ export const stationCommonTiles = [
66
+ {
67
+ type: 'station-field',
68
+ labelKey: 'label.modeller-part-field',
69
+ icon: ICON_FIELD,
70
+ description: 'one value from the station — pick which one',
71
+ group: 'station',
72
+ model: { type: 'station-field', ...at(40, 40, 300, 90), field: '', label: '', valueSize: 26 }
73
+ },
74
+ {
75
+ type: 'station-who',
76
+ labelKey: 'label.modeller-part-who',
77
+ icon: ICON_WHO,
78
+ description: 'who is scanned in at this station',
79
+ group: 'station',
80
+ model: { type: 'station-who', ...at(40, 150, 300, 110), label: '', prompt: '' }
81
+ },
82
+ {
83
+ type: 'station-counts',
84
+ labelKey: 'label.modeller-part-counts',
85
+ icon: ICON_COUNTS,
86
+ description: 'good and reject counted so far',
87
+ group: 'station',
88
+ model: { type: 'station-counts', ...at(360, 40, 300, 110), label: '' }
89
+ },
90
+ {
91
+ type: 'station-numpad',
92
+ labelKey: 'label.modeller-part-numpad',
93
+ icon: ICON_NUMPAD,
94
+ description: 'record a count without a keyboard',
95
+ group: 'station',
96
+ /*
97
+ * 높이는 **저작 검사가 요구하는 최소 위**로 잡는다. 이 검사는 숫자판을 3×4 로 보고 머리 72 를
98
+ * 뺀 뒤 칸이 44 이상인지 본다(`station-design-check`). 240 이던 때는 칸이 42 라, 저작자가 팔레트
99
+ * 에서 놓자마자 「입력 영역을 44px 이상으로 하십시오」가 떴다 — 처음 놓은 것이 이미 틀렸다고
100
+ * 말하는 저작면은 그 검사를 안 믿게 만든다. 260 이면 칸이 47 이다.
101
+ */
102
+ model: { type: 'station-numpad', ...at(360, 160, 300, 260), label: '', counts: '' }
103
+ },
104
+ {
105
+ /*
106
+ * 그리지 않는 부품이다 — 이 화면이 무엇을 얼마나 자주 읽을지를 정한다.
107
+ *
108
+ * 저작면에서는 자리를 차지하지만 작업대에서는 안 보인다. 팔레트에 있어야 하는 이유는, 없으면
109
+ * 저작자가 갱신 주기를 손댈 방법이 아예 없기 때문이다.
110
+ */
111
+ type: 'station-context',
112
+ labelKey: 'label.modeller-part-context',
113
+ icon: ICON_CONTEXT,
114
+ description: 'not drawn — sets what this screen reads and how often',
115
+ group: 'station',
116
+ model: { type: 'station-context', ...at(40, 630, 200, 60), showFields: '', refreshSeconds: 10 }
117
+ }
118
+ ];
119
+ //# sourceMappingURL=station-palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-palette.js","sourceRoot":"","sources":["../../client/station/station-palette.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,QAAQ,EACT,MAAM,0BAA0B,CAAA;AAIjC;;;;;GAKG;AACH,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;CAsBZ,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,kGAAkG;IAC/G,IAAI;IACJ,SAAS,EAAE,EAAE;CACd,CAAA;AAED,0CAA0C;AAC1C,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,KAAa,EAAE,MAAc,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;AAEvG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAuB;IACpD;QACE,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,2BAA2B;QACrC,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,6CAA6C;QAC1D,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;KAC9F;IACD;QACE,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,yBAAyB;QACnC,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,mCAAmC;QAChD,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;KAChF;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,4BAA4B;QACtC,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,gCAAgC;QAC7C,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;KACvE;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,4BAA4B;QACtC,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,mCAAmC;QAChD,KAAK,EAAE,SAAS;QAChB;;;;;WAKG;QACH,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;KACpF;IACD;QACE;;;;;WAKG;QACH,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,6BAA6B;QACvC,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,uDAAuD;QACpE,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;KAChG;CACF,CAAA","sourcesContent":["/**\n * 저작면 팔레트에 서는 **기계의 타일 다섯**과 그 그룹 (ADR-0083 결정 6).\n *\n * ── 어떻게 서나 ─────────────────────────────────────────────────────────────\n * 이 꾸러미의 `package.json` 에 `\"things-scene\": true` 가 있고 뿌리에 `things-scene.config.js` 가\n * 있으면, `@things-factory/env` 가 의존 꾸러미를 훑어 이것을 모으고 보드 모델러가 세운다. 꾸러미\n * 42 개가 이미 그 길이다.\n *\n * operato-plant 는 손으로 등록했었다 — 그 부품이 **앱 안에 있어서** 그물에 안 걸렸기 때문이다. 옮기고\n * 나면 그 까닭이 없어진다. 플랫폼에 길이 있는데 옆에 사설 수집기를 세우지 않는다.\n *\n * ── 제품의 타일은 여기 없다 ─────────────────────────────────────────────────\n * 작업지시 · 정비 · 자재 · 지시서 · 동작은 구조를 읽으므로 제품의 것이고, 제품이\n * `registerStationTiles` 로 건다. 가르는 잣대는 **구조를 읽는가**다.\n *\n * ── 이름표는 열쇠다 ─────────────────────────────────────────────────────────\n * `labelKey` 를 항목이 들고 온다(UX 판정 2026-09-20). 값으로 굳히면 언어를 바꿨을 때 옛말이 남는다 —\n * 등록은 기동 때 한 번이고 언어는 그 뒤에도 바뀐다. 손으로 적은 둘째 지도를 두지 않으려는 것이기도\n * 하다: 지도가 안 갈리면 놓인 타일이 이름 대신 `station-counts` 로 뜬다.\n */\n\nimport {\n ICON_CONTEXT,\n ICON_COUNTS,\n ICON_FIELD,\n ICON_NUMPAD,\n ICON_WHO\n} from './scene/station-icons.js'\n\nimport type { StationTileEntry } from './station-tile-registry.js'\n\n/*\n * 그룹 아이콘 — 작업대 위의 태블릿.\n *\n * 다른 그룹 아이콘과 같은 붓을 쓴다: 주된 것은 굵게(2.5), 윤곽은 가늘게(1.0), 강조는 채운다.\n * `{{strokeColor}}` 는 모델러가 테마 색으로 바꿔 넣는다.\n */\nconst icon = `\n<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" viewBox=\"0 0 30 30\" style=\"enable-background:new 0 0 30 30;\" xml:space=\"preserve\">\n <style type=\"text/css\">\n .st0{fill:{{strokeColor}};}\n .st14{fill:none;stroke:{{strokeColor}};stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}\n .st15{fill:none;stroke:{{strokeColor}};stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}\n </style>\n <g>\n <!-- 화면 — 이 그룹의 주인공이라 굵게 -->\n <rect class=\"st14\" x=\"6\" y=\"4.5\" width=\"18\" height=\"12\" rx=\"1.5\"/>\n <!-- 화면 안의 한 줄. 작업자가 읽는 값이 있다는 뜻 -->\n <line class=\"st15\" x1=\"9\" y1=\"8.5\" x2=\"17\" y2=\"8.5\"/>\n <line class=\"st15\" x1=\"9\" y1=\"12\" x2=\"21\" y2=\"12\"/>\n <!-- 받침대 -->\n <line class=\"st15\" x1=\"15\" y1=\"16.5\" x2=\"15\" y2=\"20\"/>\n <!-- 작업대 상판 -->\n <rect class=\"st0\" x=\"4\" y=\"20\" width=\"22\" height=\"2\"/>\n <!-- 다리 -->\n <line class=\"st15\" x1=\"7\" y1=\"22\" x2=\"7\" y2=\"26.5\"/>\n <line class=\"st15\" x1=\"23\" y1=\"22\" x2=\"23\" y2=\"26.5\"/>\n </g>\n</svg>\n`\n\nexport const stationGroup = {\n name: 'station',\n description: 'what an operator sees at a workbench — the values, the prompts, and the buttons that record work',\n icon,\n templates: []\n}\n\n/** 놓았을 때의 자리와 크기. 겹치지 않게 조금씩 어긋나 떨어진다. */\nconst at = (left: number, top: number, width: number, height: number) => ({ left, top, width, height })\n\n/**\n * 기본 크기는 **작업대에서 읽히는 크기**로 잡는다. 저작자가 놓자마자 줄여야 한다면 그 기본값이 틀린\n * 것이다 — 현장 화면은 한 걸음 떨어져서 읽는다.\n *\n * `equipment` 를 비워 둔다. 비면 그 화면이 걸린 작업대를 따르고, 적으면 그 설비에 못 박힌다.\n */\nexport const stationCommonTiles: StationTileEntry[] = [\n {\n type: 'station-field',\n labelKey: 'label.modeller-part-field',\n icon: ICON_FIELD,\n description: 'one value from the station — pick which one',\n group: 'station',\n model: { type: 'station-field', ...at(40, 40, 300, 90), field: '', label: '', valueSize: 26 }\n },\n {\n type: 'station-who',\n labelKey: 'label.modeller-part-who',\n icon: ICON_WHO,\n description: 'who is scanned in at this station',\n group: 'station',\n model: { type: 'station-who', ...at(40, 150, 300, 110), label: '', prompt: '' }\n },\n {\n type: 'station-counts',\n labelKey: 'label.modeller-part-counts',\n icon: ICON_COUNTS,\n description: 'good and reject counted so far',\n group: 'station',\n model: { type: 'station-counts', ...at(360, 40, 300, 110), label: '' }\n },\n {\n type: 'station-numpad',\n labelKey: 'label.modeller-part-numpad',\n icon: ICON_NUMPAD,\n description: 'record a count without a keyboard',\n group: 'station',\n /*\n * 높이는 **저작 검사가 요구하는 최소 위**로 잡는다. 이 검사는 숫자판을 3×4 로 보고 머리 72 를\n * 뺀 뒤 칸이 44 이상인지 본다(`station-design-check`). 240 이던 때는 칸이 42 라, 저작자가 팔레트\n * 에서 놓자마자 「입력 영역을 44px 이상으로 하십시오」가 떴다 — 처음 놓은 것이 이미 틀렸다고\n * 말하는 저작면은 그 검사를 안 믿게 만든다. 260 이면 칸이 47 이다.\n */\n model: { type: 'station-numpad', ...at(360, 160, 300, 260), label: '', counts: '' }\n },\n {\n /*\n * 그리지 않는 부품이다 — 이 화면이 무엇을 얼마나 자주 읽을지를 정한다.\n *\n * 저작면에서는 자리를 차지하지만 작업대에서는 안 보인다. 팔레트에 있어야 하는 이유는, 없으면\n * 저작자가 갱신 주기를 손댈 방법이 아예 없기 때문이다.\n */\n type: 'station-context',\n labelKey: 'label.modeller-part-context',\n icon: ICON_CONTEXT,\n description: 'not drawn — sets what this screen reads and how often',\n group: 'station',\n model: { type: 'station-context', ...at(40, 630, 200, 60), showFields: '', refreshSeconds: 10 }\n }\n]\n"]}
@@ -0,0 +1,47 @@
1
+ import type { StationAction, StationSnapshot } from './station-context';
2
+ /**
3
+ * 컨텍스트를 쥐고 있는 것 — **인터페이스와 접근자만. 여기도 아무것도 부르지 않는다.**
4
+ *
5
+ * ── 왜 접근자인가 ──────────────────────────────────────────────────────────
6
+ * 보드의 씬 컴포넌트는 Lit 트리 안에 없다. 캔버스 위에 그려지는 도형이라 DOM 을 타고 내려오는 컨텍스트가
7
+ * 닿지 않는다. 그래서 **주입해 두고 아무 데서나 꺼내 쓰는 자리**를 하나 만든다 — 트윈의 커맨드 채널이
8
+ * 같은 이유로 같은 모양이다.
9
+ *
10
+ * 그 덕분에 접근 방법이 하나가 된다:
11
+ *
12
+ * ```
13
+ * Lit 화면 ─┐
14
+ * 조각 ─┼→ stationRuntime() → 같은 한 장, 같은 조작
15
+ * 씬 컴포넌트 ┘
16
+ * ```
17
+ *
18
+ * ── 여러 대가 한 화면에 있을 수 있다 ───────────────────────────────────────
19
+ * 라인 전체를 한 보드에 그리면 설비마다 런타임이 하나씩 있어야 한다. 그래서 설비 이름으로 찾는다.
20
+ * 이름을 주지 않으면 **가장 최근에 열린 것**을 준다 — 태블릿 한 대에 화면 하나인 흔한 경우다.
21
+ */
22
+ export interface StationRuntime<Ground = unknown> {
23
+ readonly equipmentName: string;
24
+ /** 지금 한 장. 매번 새 객체를 돌려주므로 조각은 앞의 것과 비교할 수 있다. */
25
+ get(): StationSnapshot<Ground>;
26
+ /** 바뀌면 부른다. 돌려주는 것으로 끊는다 — 끊지 않으면 떠난 화면이 계속 그린다. */
27
+ subscribe(listener: (snapshot: StationSnapshot<Ground>) => void): () => void;
28
+ /** 서버에서 다시 읽는다. */
29
+ refresh(): Promise<void>;
30
+ /** 스캔한 값 하나. 그것이 사람인지 지시인지는 런타임이 규칙으로 가른다. */
31
+ scan(value: string): Promise<void>;
32
+ /** 목록에서 골랐을 때. */
33
+ chooseOrder(name: string): Promise<void>;
34
+ /** 현장에서 하는 조작. 사실을 남기는 자리는 이 하나뿐이다. */
35
+ act(action: StationAction): Promise<void>;
36
+ }
37
+ /** 호스트가 이 설비의 런타임을 걸어 둔다. */
38
+ export declare function provideStationRuntime(runtime: StationRuntime): void;
39
+ /** 화면을 떠날 때 치운다. 남겨 두면 다음 화면이 죽은 런타임을 잡는다. */
40
+ export declare function revokeStationRuntime(equipmentName: string): void;
41
+ /**
42
+ * 런타임을 꺼낸다. **없으면 없다고 돌려준다** — 빈 것을 지어내면 조각이 0 을 그리고, 그 0 은 「아직
43
+ * 안 붙었다」가 아니라 「오늘 아무것도 안 나왔다」로 읽힌다.
44
+ */
45
+ export declare function stationRuntime(equipmentName?: string): StationRuntime | undefined;
46
+ /** Hear about attach and revoke. The returned function stops listening. */
47
+ export declare function watchStationRuntimes(listener: () => void): () => void;
@@ -0,0 +1,47 @@
1
+ const runtimes = new Map();
2
+ let latest;
3
+ /** 호스트가 이 설비의 런타임을 걸어 둔다. */
4
+ export function provideStationRuntime(runtime) {
5
+ runtimes.set(runtime.equipmentName, runtime);
6
+ latest = runtime.equipmentName;
7
+ announce();
8
+ }
9
+ /** 화면을 떠날 때 치운다. 남겨 두면 다음 화면이 죽은 런타임을 잡는다. */
10
+ export function revokeStationRuntime(equipmentName) {
11
+ runtimes.delete(equipmentName);
12
+ if (latest === equipmentName) {
13
+ latest = [...runtimes.keys()].pop();
14
+ }
15
+ announce();
16
+ }
17
+ /**
18
+ * 런타임을 꺼낸다. **없으면 없다고 돌려준다** — 빈 것을 지어내면 조각이 0 을 그리고, 그 0 은 「아직
19
+ * 안 붙었다」가 아니라 「오늘 아무것도 안 나왔다」로 읽힌다.
20
+ */
21
+ export function stationRuntime(equipmentName) {
22
+ const key = equipmentName ?? latest;
23
+ return key ? runtimes.get(key) : undefined;
24
+ }
25
+ /**
26
+ * Who wants to hear that the set of runtimes changed — **the fix for arriving too early.**
27
+ *
28
+ * A board's scene components are compiled the moment the board model loads, which can be before the
29
+ * host has attached a runtime, and they are compiled again whenever the board is re-rendered. A
30
+ * component that looks the runtime up once at that moment and finds nothing would draw "not
31
+ * connected" for the rest of the shift, with no reason to ever look again.
32
+ *
33
+ * So attaching and revoking are announced. A component binds when it is added, and binds again when
34
+ * this fires.
35
+ */
36
+ const watchers = new Set();
37
+ /** Hear about attach and revoke. The returned function stops listening. */
38
+ export function watchStationRuntimes(listener) {
39
+ watchers.add(listener);
40
+ return () => watchers.delete(listener);
41
+ }
42
+ function announce() {
43
+ for (const listener of [...watchers]) {
44
+ listener();
45
+ }
46
+ }
47
+ //# sourceMappingURL=station-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-runtime.js","sourceRoot":"","sources":["../../client/station/station-runtime.ts"],"names":[],"mappings":"AA4CA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAA;AAClD,IAAI,MAA0B,CAAA;AAE9B,6BAA6B;AAC7B,MAAM,UAAU,qBAAqB,CAAC,OAAuB;IAC3D,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;IAC5C,MAAM,GAAG,OAAO,CAAC,aAAa,CAAA;IAC9B,QAAQ,EAAE,CAAA;AACZ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,oBAAoB,CAAC,aAAqB;IACxD,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IAE9B,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;QAC7B,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;IACrC,CAAC;IAED,QAAQ,EAAE,CAAA;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,aAAsB;IACnD,MAAM,GAAG,GAAG,aAAa,IAAI,MAAM,CAAA;IAEnC,OAAO,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAc,CAAA;AAEtC,2EAA2E;AAC3E,MAAM,UAAU,oBAAoB,CAAC,QAAoB;IACvD,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAEtB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACxC,CAAC;AAED,SAAS,QAAQ;IACf,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;QACrC,QAAQ,EAAE,CAAA;IACZ,CAAC;AACH,CAAC","sourcesContent":["import type { StationAction, StationSnapshot } from './station-context'\n\n/**\n * 컨텍스트를 쥐고 있는 것 — **인터페이스와 접근자만. 여기도 아무것도 부르지 않는다.**\n *\n * ── 왜 접근자인가 ──────────────────────────────────────────────────────────\n * 보드의 씬 컴포넌트는 Lit 트리 안에 없다. 캔버스 위에 그려지는 도형이라 DOM 을 타고 내려오는 컨텍스트가\n * 닿지 않는다. 그래서 **주입해 두고 아무 데서나 꺼내 쓰는 자리**를 하나 만든다 — 트윈의 커맨드 채널이\n * 같은 이유로 같은 모양이다.\n *\n * 그 덕분에 접근 방법이 하나가 된다:\n *\n * ```\n * Lit 화면 ─┐\n * 조각 ─┼→ stationRuntime() → 같은 한 장, 같은 조작\n * 씬 컴포넌트 ┘\n * ```\n *\n * ── 여러 대가 한 화면에 있을 수 있다 ───────────────────────────────────────\n * 라인 전체를 한 보드에 그리면 설비마다 런타임이 하나씩 있어야 한다. 그래서 설비 이름으로 찾는다.\n * 이름을 주지 않으면 **가장 최근에 열린 것**을 준다 — 태블릿 한 대에 화면 하나인 흔한 경우다.\n */\nexport interface StationRuntime<Ground = unknown> {\n readonly equipmentName: string\n\n /** 지금 한 장. 매번 새 객체를 돌려주므로 조각은 앞의 것과 비교할 수 있다. */\n get(): StationSnapshot<Ground>\n\n /** 바뀌면 부른다. 돌려주는 것으로 끊는다 — 끊지 않으면 떠난 화면이 계속 그린다. */\n subscribe(listener: (snapshot: StationSnapshot<Ground>) => void): () => void\n\n /** 서버에서 다시 읽는다. */\n refresh(): Promise<void>\n\n /** 스캔한 값 하나. 그것이 사람인지 지시인지는 런타임이 규칙으로 가른다. */\n scan(value: string): Promise<void>\n\n /** 목록에서 골랐을 때. */\n chooseOrder(name: string): Promise<void>\n\n /** 현장에서 하는 조작. 사실을 남기는 자리는 이 하나뿐이다. */\n act(action: StationAction): Promise<void>\n}\n\nconst runtimes = new Map<string, StationRuntime>()\nlet latest: string | undefined\n\n/** 호스트가 이 설비의 런타임을 걸어 둔다. */\nexport function provideStationRuntime(runtime: StationRuntime): void {\n runtimes.set(runtime.equipmentName, runtime)\n latest = runtime.equipmentName\n announce()\n}\n\n/** 화면을 떠날 때 치운다. 남겨 두면 다음 화면이 죽은 런타임을 잡는다. */\nexport function revokeStationRuntime(equipmentName: string): void {\n runtimes.delete(equipmentName)\n\n if (latest === equipmentName) {\n latest = [...runtimes.keys()].pop()\n }\n\n announce()\n}\n\n/**\n * 런타임을 꺼낸다. **없으면 없다고 돌려준다** — 빈 것을 지어내면 조각이 0 을 그리고, 그 0 은 「아직\n * 안 붙었다」가 아니라 「오늘 아무것도 안 나왔다」로 읽힌다.\n */\nexport function stationRuntime(equipmentName?: string): StationRuntime | undefined {\n const key = equipmentName ?? latest\n\n return key ? runtimes.get(key) : undefined\n}\n\n/**\n * Who wants to hear that the set of runtimes changed — **the fix for arriving too early.**\n *\n * A board's scene components are compiled the moment the board model loads, which can be before the\n * host has attached a runtime, and they are compiled again whenever the board is re-rendered. A\n * component that looks the runtime up once at that moment and finds nothing would draw \"not\n * connected\" for the rest of the shift, with no reason to ever look again.\n *\n * So attaching and revoking are announced. A component binds when it is added, and binds again when\n * this fires.\n */\nconst watchers = new Set<() => void>()\n\n/** Hear about attach and revoke. The returned function stops listening. */\nexport function watchStationRuntimes(listener: () => void): () => void {\n watchers.add(listener)\n\n return () => watchers.delete(listener)\n}\n\nfunction announce(): void {\n for (const listener of [...watchers]) {\n listener()\n }\n}\n"]}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * What a board's components get to see — **the context, as the scene knows it.**
3
+ *
4
+ * ── 씬이 생기는 순간에 건넨다 ──────────────────────────────────────────────
5
+ * 뷰어가 `scene-changed` 를 낼 때 `RootContainer.registerService` 로 건네고 한 번 다시 그리게 한다.
6
+ * 컴포넌트는 읽을 때마다 레지스트리를 보므로(`StationContextBinding`) 늦게 붙어도 잡힌다.
7
+ *
8
+ * It replaces a module-level accessor, and the reason is not tidiness. This app runs MDI by default.
9
+ * Two station boards open on two machines, and a global lookup with no equipment name hands back
10
+ * "whichever opened last" — so a component the author left unnamed draws one machine's numbers and
11
+ * **records against the other**. A registry that belongs to the scene cannot cross those wires,
12
+ * because each page holds its own scene.
13
+ *
14
+ * ── Why the formatted labels come along ────────────────────────────────────
15
+ * `values` is the same map the no-code bindings run on, already turned into words — times included.
16
+ * Times differ per app (`client/when.ts`) and the scene folder is kept clear of that, so a component
17
+ * that formatted its own would end up disagreeing with every other screen. It reads, it does not
18
+ * format.
19
+ *
20
+ * ── Custom contexts arrive by the same door ────────────────────────────────
21
+ * `root.registerService('acme', …)` and `getService('acme')`. Nothing here is station-specific
22
+ * except the name, and nothing in things-scene needs changing to add one.
23
+ */
24
+ import type { StationAction, StationSnapshot } from './station-context';
25
+ /** The name this context travels under. Other domains take other names. */
26
+ export declare const STATION_SERVICE = "station";
27
+ /**
28
+ * The name a board's bindings reach the context component by — `source: '#station-context'`.
29
+ *
30
+ * ── Why it is not just `station` ───────────────────────────────────────────
31
+ * That word is already a **data branch**. The host pushes `scene.data` keyed by component id
32
+ * (`station`, `order`, `today`, …) and the context component adds itself to the same index those
33
+ * lookups use — so naming it `station` made the branch land on the component and replace everything
34
+ * it had published. Measured: a binding for `content.체결 토크` drew the equipment name.
35
+ *
36
+ * It lives here, beside the branch names, so the two can be checked against each other without
37
+ * loading any scene code.
38
+ */
39
+ export declare const STATION_SOURCE_NAME = "station-context";
40
+ export interface StationSceneContext {
41
+ /** 저작 미리보기는 조회만 허용한다. */
42
+ readonly readOnly?: boolean;
43
+ /** Which machine this board is drawing. */
44
+ readonly equipmentName: string;
45
+ /** The whole picture, for components that need its shape. */
46
+ get snapshot(): StationSnapshot;
47
+ /** The same picture as finished labels — `order.name`, `today.good`, and the rest. */
48
+ get values(): Record<string, string | number>;
49
+ /** Leaving a fact. The one door, the same one the tile screen goes through. */
50
+ act(action: StationAction): Promise<void>;
51
+ /** Something changed. The returned function stops listening. */
52
+ subscribe(listener: () => void): () => void;
53
+ }
54
+ /**
55
+ * Find the context from inside a component.
56
+ *
57
+ * Two places, in the order they become true. The model is where the page puts it before the scene
58
+ * opens, so it is the one that answers on the first frame. The service registry
59
+ * (`RootContainer.registerService`) is the late door — for a host that attaches after the fact, or
60
+ * for a second context put there by someone else.
61
+ *
62
+ * `root.model.model` is where a key added to the board model comes to rest: `create()` wraps the
63
+ * board model inside the root's own model. Ugly, and it is written down once, here, rather than in
64
+ * every component.
65
+ */
66
+ export declare function sceneContextOf<T = StationSceneContext>(component: {
67
+ root?: any;
68
+ }, name?: string): T | undefined;