@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
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @things-factory/station
2
+
3
+ 현장 화면 저작의 **기계** 한 벌. 제품은 **선언 다섯**을 준다 (ADR-0083).
4
+
5
+ 공장(operato-plant)에서 먼저 섰고, 창고 · 야드 · 에너지가 같은 것을 쓴다. 베끼지 않는다 — 네 벌이
6
+ 갈라지면 합칠 수 없다.
7
+
8
+ ## 무엇이 여기 있나
9
+
10
+ 화면 정의와 그 판(spec · draft · layout · thumbnail) · 흐름 모델과 검사 · 배정 · 런타임 바인딩 ·
11
+ 저작 결재의 기계(release gate · record · signature)와 그 결재 양식 둘 · 채점표.
12
+
13
+ **모듈은 제품의 엔티티를 import 하지 않는다.** 시험이 그것을 지킨다
14
+ (`test/station-machine-boundary.test.ts`).
15
+
16
+ ## 제품이 주는 선언 다섯
17
+
18
+ `declareStationProduct` 를 기동 경로에서 한 번 부른다. 다섯 중 하나라도 비면 **그 자리에서 말한다** —
19
+ 기본값은 없다. 기본값이 있으면 창고 화면이 공장의 문을 조용히 내놓거나, 아무것도 안 내놓고
20
+ 「이 제품에는 서비스가 없다」로 읽힌다.
21
+
22
+ | | 무엇 | 왜 제품의 것인가 |
23
+ |---|---|---|
24
+ | 1 | **축** | 화면이 읽을 수 있는 값의 닫힌 목록. 공장은 설비 · 공정, 창고는 로케이션 · 작업 |
25
+ | 2 | **부를 수 있는 문** | 걸음이 부를 수 있는 mutation 의 목록 |
26
+ | 3 | **역할** | 누가 저작하고 누가 서명하나. 결재선이 어디로 가는지 |
27
+ | 4 | **바탕을 읽는 자리** (`readGround`) | 어느 설비 · 누가 왔나 · 지금 무엇을 만드나. 공장은 작업지시와 생산실적, 창고는 작업과 주문 |
28
+ | 5 | **기록을 남기는 자리** (`recordChange`) | 무엇이 바뀌었는지는 기계가 만들고, 그것이 **어느 이력으로 가는지**는 제품이 정한다 |
29
+
30
+ ```ts
31
+ declareStationProduct({
32
+ product: 'operato-plant',
33
+ services: STATION_SERVICES,
34
+ readGround: async (manager, domain, stationName, at) => ({ workCenterName, routeKey, ... }),
35
+ recordChange: async (manager, domain, what, by) => noteFactChanges(manager, domain, what, { ...by, source: SOURCE_MANUAL })
36
+ })
37
+ ```
38
+
39
+ ## 제품마다 무엇을 구성하고, 고객은 무엇을 바꾸나
40
+
41
+ 층이 셋이다. **아래로 갈수록 코드가 줄고 사람이 늘어난다.**
42
+
43
+ ### 1층 · 제품(앱)이 코드로 선언한다
44
+
45
+ plant · warehouse · yms · ems 가 각자 한 번씩 한다. 기계에 기본값은 없고, 빈 자리가 있으면
46
+ **기동 때 거절한다** — 조용히 빈 화면이 되는 쪽보다 안 서는 쪽이 낫다.
47
+
48
+ ```
49
+ 서버 declareStationProduct 축 · 부를 수 있는 문 · 역할 · 바탕을 읽는 자리 · 기록을 남기는 자리
50
+ 화면 declareStationClientProduct 바탕 읽기 · 스캔 판정 · 값 펴기 · 타일 목록
51
+ 타입 StationSnapshot<Ground> 제품의 바탕 모양. 기계는 들고만 다니고 안을 안 읽는다
52
+ ```
53
+
54
+ ### 2층 · 부품
55
+
56
+ ```
57
+ 공통 여섯 모듈이 든다. package.json 의 "things-scene" 과 things-scene.config.js 로
58
+ 플랫폼 그물이 걷어 저작면 팔레트에 세운다
59
+ 제품 다섯 제품이 짓고 자기 선언으로 건다. 구조를 읽는 것들 — 작업지시 · 정비 · 자재 · 지시서 · 동작
60
+ ```
61
+
62
+ 가르는 잣대는 **구조를 읽는가**다. 축 값(누가 · 어디 · 오늘 얼마나)만 읽으면 어느 제품에서나 그대로
63
+ 서고, 덩어리를 읽으면 제품의 것이다. 새 부품을 더하는 자리도 여기다.
64
+
65
+ ### 3층 · 고객(설치본)이 코드 없이 한다 — 이것이 이 기능의 본체다
66
+
67
+ ```
68
+ 화면 저작 보드에 부품을 놓고 축 이름을 붙인다 (order.name · today.good)
69
+ 흐름 단계를 잇고 그 걸음이 부를 문을 고른다
70
+ 배정 어느 설비 · 공정 · 경로에 이 화면이 서는가
71
+ 발행 승인 통제 문서면 결재를 지나야 현장에 나간다
72
+ 스캔 규칙 사번 접두사 · 자릿수 · 라벨 모양 — 코드가 아니라 값
73
+ ```
74
+
75
+ **고객마다 다른 것을 코드에 적으면 고객마다 화면을 새로 만들게 된다.** 사번에 접두사를 붙이는 공장이
76
+ 있고 자릿수로 가르는 공장이 있다. 그것을 화면 코드에 적는 순간 이 기능은 no-code 가 아니라 캔버스를
77
+ 앞에 세운 우리 코드가 된다. 그래서 규칙을 값으로 두고 화면은 읽기만 한다.
78
+
79
+ ### 아직 안 정해진 것
80
+
81
+ `act()` 의 **몸통**. 동작 이름 여섯(`start` · `count` · `pause` · `unstage` · `resume` · `finish`)은
82
+ 창고에서도 서는 말인데, 그 안은 `startTask({ jobOrderName: … })` 처럼 제품의 인자 이름을 직접 짓는다.
83
+ 제품이 그것을 어떻게 갈아 끼우는지가 1층에 아직 없다.
84
+
85
+ ## 배정 축은 표준의 총칭이다
86
+
87
+ `workCenterName` 이다 — `productionLineName` 이 아니다. 계약이 `WorkCenter` 를
88
+ `ProcessCell` · `ProductionLine` · `ProductionUnit` · `StorageZone` 의 총칭으로 적는다. 갈래를 축 이름에
89
+ 박으면 창고 존과 야드 구역이 들어올 자리가 없다. 합집합은 두지 않는다 — 축이 늘 때마다 제품 넷이
90
+ 같이 늘기 때문이다 (ADR-0083 결정 5).
91
+
92
+ ## 이 판이 온 자리 (2026-09-20, ADR-0083 걸음 3)
93
+
94
+ `operato-plant` 에서 **제자리에서 선을 그은 뒤**(걸음 2) 옮겼다. 옮기면서 가르면 깨졌을 때 옮겨서
95
+ 깨진 건지 갈라서 깨진 건지 알 수 없다.
96
+
97
+ ```
98
+ 서버 기계 26 파일 plant `server/service/station/` 에서
99
+ 저작 결재 양식 2 파일 plant `server/activities/` 에서 (ADR-0083 결정 7)
100
+ 축·흐름 검사 4 파일 plant `shared/` 에서 — 서버와 브라우저가 같은 파일을 읽는다
101
+ 시험 15 파일 124 통과
102
+ 낱말표 39 개 키 위에 주인이 있는 13 개는 안 가져왔다(마지막이 이기는 병합이라 남의 화면을 덮는다)
103
+ ```
104
+
105
+ **plant 에 남는 것** — 선언 다섯의 구현(`station-context-service` 등 8 파일) · 컴포저 · 모델러 ·
106
+ 현장 화면 페이지 · 씬 컴포넌트.
107
+
108
+ ## 아직 안 온 것
109
+
110
+ - **클라이언트.** `client/station` 55 파일 6,257 줄. 씬 열하나 중 여섯이 공통이고 다섯이 제품의 것이다.
111
+ 그 확장점의 모양을 UX 레인이 정한다 (ADR-0083 결정 6).
112
+ - **컴포저 · 모델러 화면.** 공용이 되는 저작 화면의 틀을 UX 레인이 본다 (ADR-0083 「열어 둔 것」).
113
+ - **`Reversibility`.** `server/service/station/reversibility.ts` 에 네 값이 있다. 같은 어휘를 plant 의 활동
114
+ 선언 열 개가 쓰고 worklist 가 역할 이름으로 갖고 있다 — 집을 수석 아키텍트가 정한다.
115
+
116
+ ## plant 가 자기 사본을 걷을 때 할 일
117
+
118
+ 발행 · 설치 뒤다. 그 전까지 plant 는 자기 것을 그대로 돌리고 여기 것은 안 부른다.
119
+
120
+ - 옮긴 26 + 2 + 4 파일과 그 시험 15 개를 지운다
121
+ - 낱말표에서 위 39 개 키를 지운다
122
+ - `required-activities.ts` 에서 저작 결재 둘을 뺀다 — 이 모듈이 스스로 등록한다
123
+ - `deletionChanges` 를 이 모듈에서 가져온다 (`activity-non-working-withdrawal` 도 쓴다)
124
+ - `declareStationProduct` 에 `recordChange` 를 채운다 — 없으면 기동 때 거절한다
@@ -0,0 +1,11 @@
1
+ export * from './station/station-tile-registry.js';
2
+ export * from './station/station-palette.js';
3
+ export * from './station/station-board-binding.js';
4
+ export * from './station/station-context.js';
5
+ export * from './station/station-runtime.js';
6
+ export * from './station/station-scene-context.js';
7
+ export * from './station/station-screen-flow.js';
8
+ export * from './station/station-work-runtime.js';
9
+ export * from './station/station-work-scenarios.js';
10
+ export * from './station/task-counts.js';
11
+ export * from './station/scene/index.js';
@@ -0,0 +1,13 @@
1
+ export * from './station/station-tile-registry.js';
2
+ export * from './station/station-palette.js';
3
+ export * from './station/station-board-binding.js';
4
+ export * from './station/station-context.js';
5
+ export * from './station/station-runtime.js';
6
+ export * from './station/station-scene-context.js';
7
+ export * from './station/station-screen-flow.js';
8
+ export * from './station/station-work-runtime.js';
9
+ export * from './station/station-work-scenarios.js';
10
+ export * from './station/task-counts.js';
11
+ /* 씬 부품 — 이 import 자체가 `@sceneComponent` 등록을 돈다. 그려지려면 이것이 불려야 한다. */
12
+ export * from './station/scene/index.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAA;AAClD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,oCAAoC,CAAA;AAClD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,oCAAoC,CAAA;AAClD,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,qCAAqC,CAAA;AACnD,cAAc,0BAA0B,CAAA;AAExC,qEAAqE;AACrE,cAAc,0BAA0B,CAAA","sourcesContent":["export * from './station/station-tile-registry.js'\nexport * from './station/station-palette.js'\nexport * from './station/station-board-binding.js'\nexport * from './station/station-context.js'\nexport * from './station/station-runtime.js'\nexport * from './station/station-scene-context.js'\nexport * from './station/station-screen-flow.js'\nexport * from './station/station-work-runtime.js'\nexport * from './station/station-work-scenarios.js'\nexport * from './station/task-counts.js'\n\n/* 씬 부품 — 이 import 자체가 `@sceneComponent` 등록을 돈다. 그려지려면 이것이 불려야 한다. */\nexport * from './station/scene/index.js'\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 보드에서 쓸 현장 컴포넌트들 — 여기서 한 번 부르면 보드 저작면에 나온다.
3
+ *
4
+ * 호스트는 이것을 부르기만 하고, 컨텍스트는 `provideStationRuntime` 으로 따로 걸어 준다.
5
+ */
6
+ import './station-context-scene';
7
+ import './station-counts-scene';
8
+ import './station-field-scene';
9
+ import './station-numpad-scene';
10
+ import './station-who-scene';
11
+ import './station-work-panel-scene';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 보드에서 쓸 현장 컴포넌트들 — 여기서 한 번 부르면 보드 저작면에 나온다.
3
+ *
4
+ * 호스트는 이것을 부르기만 하고, 컨텍스트는 `provideStationRuntime` 으로 따로 걸어 준다.
5
+ */
6
+ import './station-context-scene';
7
+ import './station-counts-scene';
8
+ import './station-field-scene';
9
+ import './station-numpad-scene';
10
+ import './station-who-scene';
11
+ import './station-work-panel-scene';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../client/station/scene/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,yBAAyB,CAAA;AAChC,OAAO,wBAAwB,CAAA;AAC/B,OAAO,uBAAuB,CAAA;AAC9B,OAAO,wBAAwB,CAAA;AAC/B,OAAO,qBAAqB,CAAA;AAC5B,OAAO,4BAA4B,CAAA","sourcesContent":["/**\n * 보드에서 쓸 현장 컴포넌트들 — 여기서 한 번 부르면 보드 저작면에 나온다.\n *\n * 호스트는 이것을 부르기만 하고, 컨텍스트는 `provideStationRuntime` 으로 따로 걸어 준다.\n */\n\nimport './station-context-scene'\nimport './station-counts-scene'\nimport './station-field-scene'\nimport './station-numpad-scene'\nimport './station-who-scene'\nimport './station-work-panel-scene'\n"]}
@@ -0,0 +1,25 @@
1
+ import type { StationSceneContext } from '../station-scene-context';
2
+ /**
3
+ * Holding on to the context from inside a component — **shared by two class families.**
4
+ *
5
+ * The drawing components descend from `Shape`, the context component from `ServiceComponent`, so
6
+ * they cannot inherit this from a common parent. It is small enough to hand around instead.
7
+ *
8
+ * ── Looked up late, deliberately ───────────────────────────────────────────
9
+ * The order is fixed and unhelpful: the viewer builds the scene, adding every component, and only
10
+ * then does the page have a scene to register the context on. Bind once when added and every
11
+ * component has looked a moment too early. So this looks on every read — a `Map.get` — and starts
12
+ * the subscription the first time it finds something.
13
+ */
14
+ export declare class StationContextBinding {
15
+ private owner;
16
+ private onChange;
17
+ private bound?;
18
+ private unsubscribe?;
19
+ /** `onChange` fires when the context reports new numbers — redraw, or push new data. */
20
+ constructor(owner: {
21
+ root?: any;
22
+ }, onChange: () => void);
23
+ get context(): StationSceneContext | undefined;
24
+ release(): void;
25
+ }
@@ -0,0 +1,39 @@
1
+ import { sceneContextOf } from '../station-scene-context';
2
+ /**
3
+ * Holding on to the context from inside a component — **shared by two class families.**
4
+ *
5
+ * The drawing components descend from `Shape`, the context component from `ServiceComponent`, so
6
+ * they cannot inherit this from a common parent. It is small enough to hand around instead.
7
+ *
8
+ * ── Looked up late, deliberately ───────────────────────────────────────────
9
+ * The order is fixed and unhelpful: the viewer builds the scene, adding every component, and only
10
+ * then does the page have a scene to register the context on. Bind once when added and every
11
+ * component has looked a moment too early. So this looks on every read — a `Map.get` — and starts
12
+ * the subscription the first time it finds something.
13
+ */
14
+ export class StationContextBinding {
15
+ /** `onChange` fires when the context reports new numbers — redraw, or push new data. */
16
+ constructor(owner, onChange) {
17
+ this.owner = owner;
18
+ this.onChange = onChange;
19
+ }
20
+ get context() {
21
+ const found = sceneContextOf(this.owner);
22
+ if (found !== this.bound) {
23
+ this.unsubscribe?.();
24
+ this.bound = found;
25
+ this.unsubscribe = found?.subscribe(this.onChange);
26
+ if (found) {
27
+ /* Newly attached — the caller has stale nothing on screen until it hears once. */
28
+ this.onChange();
29
+ }
30
+ }
31
+ return this.bound;
32
+ }
33
+ release() {
34
+ this.unsubscribe?.();
35
+ this.unsubscribe = undefined;
36
+ this.bound = undefined;
37
+ }
38
+ }
39
+ //# sourceMappingURL=station-context-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-context-binding.js","sourceRoot":"","sources":["../../../client/station/scene/station-context-binding.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEzD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,qBAAqB;IAIhC,wFAAwF;IACxF,YACU,KAAqB,EACrB,QAAoB;QADpB,UAAK,GAAL,KAAK,CAAgB;QACrB,aAAQ,GAAR,QAAQ,CAAY;IAC3B,CAAC;IAEJ,IAAI,OAAO;QACT,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAExC,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,WAAW,EAAE,EAAE,CAAA;YACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;YAClB,IAAI,CAAC,WAAW,GAAG,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAElD,IAAI,KAAK,EAAE,CAAC;gBACV,kFAAkF;gBAClF,IAAI,CAAC,QAAQ,EAAE,CAAA;YACjB,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,OAAO;QACL,IAAI,CAAC,WAAW,EAAE,EAAE,CAAA;QACpB,IAAI,CAAC,WAAW,GAAG,SAAS,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;IACxB,CAAC;CACF","sourcesContent":["import type { StationSceneContext } from '../station-scene-context'\nimport { sceneContextOf } from '../station-scene-context'\n\n/**\n * Holding on to the context from inside a component — **shared by two class families.**\n *\n * The drawing components descend from `Shape`, the context component from `ServiceComponent`, so\n * they cannot inherit this from a common parent. It is small enough to hand around instead.\n *\n * ── Looked up late, deliberately ───────────────────────────────────────────\n * The order is fixed and unhelpful: the viewer builds the scene, adding every component, and only\n * then does the page have a scene to register the context on. Bind once when added and every\n * component has looked a moment too early. So this looks on every read — a `Map.get` — and starts\n * the subscription the first time it finds something.\n */\nexport class StationContextBinding {\n private bound?: StationSceneContext\n private unsubscribe?: () => void\n\n /** `onChange` fires when the context reports new numbers — redraw, or push new data. */\n constructor(\n private owner: { root?: any },\n private onChange: () => void\n ) {}\n\n get context(): StationSceneContext | undefined {\n const found = sceneContextOf(this.owner)\n\n if (found !== this.bound) {\n this.unsubscribe?.()\n this.bound = found\n this.unsubscribe = found?.subscribe(this.onChange)\n\n if (found) {\n /* Newly attached — the caller has stale nothing on screen until it hears once. */\n this.onChange()\n }\n }\n\n return this.bound\n }\n\n release() {\n this.unsubscribe?.()\n this.unsubscribe = undefined\n this.bound = undefined\n }\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import { ComponentNature, ServiceComponent } from '@hatiolab/things-scene';
2
+ /** What the host reads off a board to decide how hard to keep it current. */
3
+ export interface StationRefreshPolicy {
4
+ everyMs?: number;
5
+ }
6
+ /**
7
+ * Read the policy straight off the board model, before the scene is built.
8
+ *
9
+ * The host needs it at open time and there are no components yet, so this looks at the JSON rather
10
+ * than at a live component.
11
+ */
12
+ export declare function stationRefreshPolicyOf(model: any): StationRefreshPolicy;
13
+ export default class StationContextScene extends ServiceComponent {
14
+ get serviceName(): string;
15
+ /**
16
+ * ── Why this is not a data source ────────────────────────────────────────
17
+ * It published the whole context for a while, so a board could bind through `#station-context`.
18
+ * That door was **redundant and broken**: redundant because the host already pushes `scene.data`
19
+ * keyed by component id, so naming a component `directive` hands it the directive and the standard
20
+ * mapping editor takes it from there; broken because a `ServiceComponent` draws nothing in view
21
+ * mode, so its `render` never ran, so nothing ever claimed the name or published anything.
22
+ *
23
+ * Two ways to do one thing, and the second one silently did nothing. Removed rather than repaired
24
+ * — the door that works is the framework's own.
25
+ */
26
+ render(context: CanvasRenderingContext2D): void;
27
+ get nature(): ComponentNature;
28
+ }
@@ -0,0 +1,138 @@
1
+ import { __decorate } from "tslib";
2
+ import { ServiceComponent, sceneComponent } from '@hatiolab/things-scene';
3
+ import { stationBindingNames } from '../station-board-binding';
4
+ import { STATION_COLOR } from './station-scene-style';
5
+ import { STATION_SERVICE } from '../station-scene-context';
6
+ /**
7
+ * On a board · **the data source the whole screen binds off.**
8
+ *
9
+ * ── What it is for ─────────────────────────────────────────────────────────
10
+ * Values reach any component whose id is a context name, and structured branches reach any component
11
+ * whose id is a branch name (`order`, `directive`, `route`, …). Both work, and both are invisible
12
+ * while a board is being drawn — nothing on the property panel says such an id means anything, so an
13
+ * author has to be told, and there are three dozen names.
14
+ *
15
+ * This lists them on the canvas while editing, and records in the board model that this board is a
16
+ * station screen. It also carries the refresh policy, which the host reads off the model when it
17
+ * opens the board.
18
+ *
19
+ * ── What is not here ───────────────────────────────────────────────────────
20
+ * It is **not** where the context comes from. The page registers that when it opens the board,
21
+ * present or not. Hanging the context off a component would mean a board drawn without one shows a
22
+ * screenful of nothing, silently, and a shop-floor screen is the last place that can happen.
23
+ *
24
+ * ── Times in `data` are raw ────────────────────────────────────────────────
25
+ * The object here is the snapshot, so `task.since` is milliseconds. Words come from the label doors
26
+ * (`station-field`, or an id binding), which run through the app's own time formatting. A board that
27
+ * wants a readable time takes it from there, not by formatting one here.
28
+ *
29
+ * ── Invisible to the operator ──────────────────────────────────────────────
30
+ * `ServiceComponent` draws an icon in edit mode and nothing in view mode. The author sees it, the
31
+ * floor does not.
32
+ */
33
+ const NATURE = {
34
+ mutable: false,
35
+ resizable: true,
36
+ rotatable: false,
37
+ properties: [
38
+ {
39
+ type: 'checkbox',
40
+ label: 'list the fields while editing',
41
+ name: 'showFields',
42
+ property: 'showFields'
43
+ },
44
+ {
45
+ /*
46
+ * How often to re-read, in seconds. Empty means only when this tablet does something.
47
+ *
48
+ * A policy and not a subscription: the clock belongs to the screen, one for the whole board.
49
+ * Per-component it would be one timer per label, and deleting the wrong box would stop the
50
+ * screen updating with nothing to say so.
51
+ */
52
+ type: 'number',
53
+ label: 'refresh every (seconds)',
54
+ name: 'refreshSeconds',
55
+ property: 'refreshSeconds'
56
+ }
57
+ ]
58
+ };
59
+ /**
60
+ * Read the policy straight off the board model, before the scene is built.
61
+ *
62
+ * The host needs it at open time and there are no components yet, so this looks at the JSON rather
63
+ * than at a live component.
64
+ */
65
+ export function stationRefreshPolicyOf(model) {
66
+ const declared = (model?.components ?? []).find((component) => component?.type === 'station-context');
67
+ const seconds = Number(declared?.refreshSeconds);
68
+ return Number.isFinite(seconds) && seconds > 0 ? { everyMs: seconds * 1000 } : {};
69
+ }
70
+ /** What this component registers itself as, so a host can ask whether a board is a station screen. */
71
+ const DECLARATION_SERVICE = `${STATION_SERVICE}-declaration`;
72
+ let StationContextScene = class StationContextScene extends ServiceComponent {
73
+ get serviceName() {
74
+ return DECLARATION_SERVICE;
75
+ }
76
+ /**
77
+ * ── Why this is not a data source ────────────────────────────────────────
78
+ * It published the whole context for a while, so a board could bind through `#station-context`.
79
+ * That door was **redundant and broken**: redundant because the host already pushes `scene.data`
80
+ * keyed by component id, so naming a component `directive` hands it the directive and the standard
81
+ * mapping editor takes it from there; broken because a `ServiceComponent` draws nothing in view
82
+ * mode, so its `render` never ran, so nothing ever claimed the name or published anything.
83
+ *
84
+ * Two ways to do one thing, and the second one silently did nothing. Removed rather than repaired
85
+ * — the door that works is the framework's own.
86
+ */
87
+ render(context) {
88
+ /* View mode draws the icon and nothing else — the base already returns early there. */
89
+ super.render(context);
90
+ const state = this.state;
91
+ if (this.app?.isViewMode || !state.showFields) {
92
+ return;
93
+ }
94
+ const { left, top, width, height } = state;
95
+ const names = stationBindingNames();
96
+ context.save();
97
+ context.beginPath();
98
+ context.rect(left, top, width, height);
99
+ context.clip();
100
+ context.fillStyle = STATION_COLOR.paper;
101
+ context.globalAlpha = 0.92;
102
+ context.fill();
103
+ context.globalAlpha = 1;
104
+ context.strokeStyle = STATION_COLOR.action;
105
+ context.stroke();
106
+ context.textBaseline = 'top';
107
+ context.textAlign = 'left';
108
+ context.fillStyle = STATION_COLOR.action;
109
+ context.font = 'bold 11px sans-serif';
110
+ context.fillText('station context — put one of these in a component id', left + 8, top + 8);
111
+ /*
112
+ * Down the columns, as many as fit. Truncating is fine and paging is not worth it — the point is
113
+ * that the names exist and roughly what they look like, not to be a reference card.
114
+ */
115
+ const lineHeight = 13;
116
+ const rows = Math.max(1, Math.floor((height - 30) / lineHeight));
117
+ const columnWidth = 130;
118
+ context.font = '10px sans-serif';
119
+ context.fillStyle = STATION_COLOR.ink;
120
+ names.forEach((name, index) => {
121
+ const x = left + 8 + Math.floor(index / rows) * columnWidth;
122
+ const y = top + 26 + (index % rows) * lineHeight;
123
+ if (x + columnWidth <= left + width + columnWidth) {
124
+ context.fillText(name, x, y);
125
+ }
126
+ });
127
+ context.textBaseline = 'alphabetic';
128
+ context.restore();
129
+ }
130
+ get nature() {
131
+ return NATURE;
132
+ }
133
+ };
134
+ StationContextScene = __decorate([
135
+ sceneComponent('station-context')
136
+ ], StationContextScene);
137
+ export default StationContextScene;
138
+ //# sourceMappingURL=station-context-scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-context-scene.js","sourceRoot":"","sources":["../../../client/station/scene/station-context-scene.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentNature, ServiceComponent, sceneComponent } from '@hatiolab/things-scene'\n\nimport { stationBindingNames } from '../station-board-binding'\nimport { STATION_COLOR } from './station-scene-style'\nimport { STATION_SERVICE } from '../station-scene-context'\n\n/**\n * On a board · **the data source the whole screen binds off.**\n *\n * ── What it is for ─────────────────────────────────────────────────────────\n * Values reach any component whose id is a context name, and structured branches reach any component\n * whose id is a branch name (`order`, `directive`, `route`, …). Both work, and both are invisible\n * while a board is being drawn — nothing on the property panel says such an id means anything, so an\n * author has to be told, and there are three dozen names.\n *\n * This lists them on the canvas while editing, and records in the board model that this board is a\n * station screen. It also carries the refresh policy, which the host reads off the model when it\n * opens the board.\n *\n * ── What is not here ───────────────────────────────────────────────────────\n * It is **not** where the context comes from. The page registers that when it opens the board,\n * present or not. Hanging the context off a component would mean a board drawn without one shows a\n * screenful of nothing, silently, and a shop-floor screen is the last place that can happen.\n *\n * ── Times in `data` are raw ────────────────────────────────────────────────\n * The object here is the snapshot, so `task.since` is milliseconds. Words come from the label doors\n * (`station-field`, or an id binding), which run through the app's own time formatting. A board that\n * wants a readable time takes it from there, not by formatting one here.\n *\n * ── Invisible to the operator ──────────────────────────────────────────────\n * `ServiceComponent` draws an icon in edit mode and nothing in view mode. The author sees it, the\n * floor does not.\n */\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: false,\n properties: [\n {\n type: 'checkbox',\n label: 'list the fields while editing',\n name: 'showFields',\n property: 'showFields'\n },\n {\n /*\n * How often to re-read, in seconds. Empty means only when this tablet does something.\n *\n * A policy and not a subscription: the clock belongs to the screen, one for the whole board.\n * Per-component it would be one timer per label, and deleting the wrong box would stop the\n * screen updating with nothing to say so.\n */\n type: 'number',\n label: 'refresh every (seconds)',\n name: 'refreshSeconds',\n property: 'refreshSeconds'\n }\n ]\n}\n\n/** What the host reads off a board to decide how hard to keep it current. */\nexport interface StationRefreshPolicy {\n everyMs?: number\n}\n\n/**\n * Read the policy straight off the board model, before the scene is built.\n *\n * The host needs it at open time and there are no components yet, so this looks at the JSON rather\n * than at a live component.\n */\nexport function stationRefreshPolicyOf(model: any): StationRefreshPolicy {\n const declared = (model?.components ?? []).find((component: any) => component?.type === 'station-context')\n const seconds = Number(declared?.refreshSeconds)\n\n return Number.isFinite(seconds) && seconds > 0 ? { everyMs: seconds * 1000 } : {}\n}\n\n/** What this component registers itself as, so a host can ask whether a board is a station screen. */\nconst DECLARATION_SERVICE = `${STATION_SERVICE}-declaration`\n\n@sceneComponent('station-context')\nexport default class StationContextScene extends ServiceComponent {\n get serviceName() {\n return DECLARATION_SERVICE\n }\n\n /**\n * ── Why this is not a data source ────────────────────────────────────────\n * It published the whole context for a while, so a board could bind through `#station-context`.\n * That door was **redundant and broken**: redundant because the host already pushes `scene.data`\n * keyed by component id, so naming a component `directive` hands it the directive and the standard\n * mapping editor takes it from there; broken because a `ServiceComponent` draws nothing in view\n * mode, so its `render` never ran, so nothing ever claimed the name or published anything.\n *\n * Two ways to do one thing, and the second one silently did nothing. Removed rather than repaired\n * — the door that works is the framework's own.\n */\n\n render(context: CanvasRenderingContext2D) {\n /* View mode draws the icon and nothing else — the base already returns early there. */\n super.render(context as any)\n\n const state: any = this.state\n\n if ((this as any).app?.isViewMode || !state.showFields) {\n return\n }\n\n const { left, top, width, height } = state\n const names = stationBindingNames()\n\n context.save()\n context.beginPath()\n context.rect(left, top, width, height)\n context.clip()\n\n context.fillStyle = STATION_COLOR.paper\n context.globalAlpha = 0.92\n context.fill()\n context.globalAlpha = 1\n context.strokeStyle = STATION_COLOR.action\n context.stroke()\n\n context.textBaseline = 'top'\n context.textAlign = 'left'\n context.fillStyle = STATION_COLOR.action\n context.font = 'bold 11px sans-serif'\n context.fillText('station context — put one of these in a component id', left + 8, top + 8)\n\n /*\n * Down the columns, as many as fit. Truncating is fine and paging is not worth it — the point is\n * that the names exist and roughly what they look like, not to be a reference card.\n */\n const lineHeight = 13\n const rows = Math.max(1, Math.floor((height - 30) / lineHeight))\n const columnWidth = 130\n\n context.font = '10px sans-serif'\n context.fillStyle = STATION_COLOR.ink\n\n names.forEach((name, index) => {\n const x = left + 8 + Math.floor(index / rows) * columnWidth\n const y = top + 26 + (index % rows) * lineHeight\n\n if (x + columnWidth <= left + width + columnWidth) {\n context.fillText(name, x, y)\n }\n })\n\n context.textBaseline = 'alphabetic'\n context.restore()\n }\n\n get nature() {\n return NATURE\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { ComponentNature } from '@hatiolab/things-scene';
2
+ import { StationScene } from './station-scene';
3
+ export default class StationCountsScene extends StationScene {
4
+ render(context: CanvasRenderingContext2D): void;
5
+ get nature(): ComponentNature;
6
+ }
@@ -0,0 +1,51 @@
1
+ import { __decorate } from "tslib";
2
+ import { sceneComponent } from '@hatiolab/things-scene';
3
+ import { STATION_COLOR, drawCard, drawDetached, drawHeading } from './station-scene-style';
4
+ import { StationScene } from './station-scene';
5
+ /**
6
+ * 보드용 · 오늘 얼마나 나왔나.
7
+ *
8
+ * 진척(%)은 그리지 않는다 — 아무도 그 값을 계산하지 않는다(아키텍트 확인, 2026-08-31). 0% 로 그리면
9
+ * 시작도 안 한 작업으로 보인다.
10
+ */
11
+ const NATURE = {
12
+ mutable: false,
13
+ resizable: true,
14
+ rotatable: false,
15
+ properties: [
16
+ { type: 'string', label: 'equipment', name: 'equipment', property: 'equipment' },
17
+ { type: 'string', label: 'label', name: 'label', property: 'label' }
18
+ ]
19
+ };
20
+ let StationCountsScene = class StationCountsScene extends StationScene {
21
+ render(context) {
22
+ const state = this.state;
23
+ const { left, top, width, height } = state;
24
+ const snapshot = this.snapshot;
25
+ if (!snapshot) {
26
+ drawDetached(context, 'not connected', left, top, width, height);
27
+ return;
28
+ }
29
+ const today = snapshot.today;
30
+ drawCard(context, left, top, width, height);
31
+ const y = drawHeading(context, state.label || 'today', left, top, width);
32
+ const half = width / 2;
33
+ context.font = 'bold 28px sans-serif';
34
+ context.fillStyle = STATION_COLOR.good;
35
+ context.fillText(String(today.good), left + 8, y + 4);
36
+ context.fillStyle = STATION_COLOR.scrap;
37
+ context.fillText(String(today.scrap), left + half + 4, y + 4);
38
+ context.font = '10px sans-serif';
39
+ context.fillStyle = STATION_COLOR.quiet;
40
+ context.fillText('good', left + 8, y + 36);
41
+ context.fillText('scrap', left + half + 4, y + 36);
42
+ }
43
+ get nature() {
44
+ return NATURE;
45
+ }
46
+ };
47
+ StationCountsScene = __decorate([
48
+ sceneComponent('station-counts')
49
+ ], StationCountsScene);
50
+ export default StationCountsScene;
51
+ //# sourceMappingURL=station-counts-scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-counts-scene.js","sourceRoot":"","sources":["../../../client/station/scene/station-counts-scene.ts"],"names":[],"mappings":";AAAA,OAAO,EAAmB,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAExE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE;QACV,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE;QAChF,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE;KACrE;CACF,CAAA;AAGc,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,YAAY;IAC1D,MAAM,CAAC,OAAiC;QACtC,MAAM,KAAK,GAAQ,IAAI,CAAC,KAAK,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAE9B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;YAChE,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;QAE5B,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAE3C,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,IAAI,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;QACxE,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAA;QAEtB,OAAO,CAAC,IAAI,GAAG,sBAAsB,CAAA;QACrC,OAAO,CAAC,SAAS,GAAG,aAAa,CAAC,IAAI,CAAA;QACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QAErD,OAAO,CAAC,SAAS,GAAG,aAAa,CAAC,KAAK,CAAA;QACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QAE7D,OAAO,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAChC,OAAO,CAAC,SAAS,GAAG,aAAa,CAAC,KAAK,CAAA;QACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;QAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;IACpD,CAAC;IAED,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA;AAlCoB,kBAAkB;IADtC,cAAc,CAAC,gBAAgB,CAAC;GACZ,kBAAkB,CAkCtC;eAlCoB,kBAAkB","sourcesContent":["import { ComponentNature, sceneComponent } from '@hatiolab/things-scene'\n\nimport { STATION_COLOR, drawCard, drawDetached, drawHeading } from './station-scene-style'\nimport { StationScene } from './station-scene'\n\n/**\n * 보드용 · 오늘 얼마나 나왔나.\n *\n * 진척(%)은 그리지 않는다 — 아무도 그 값을 계산하지 않는다(아키텍트 확인, 2026-08-31). 0% 로 그리면\n * 시작도 안 한 작업으로 보인다.\n */\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: false,\n properties: [\n { type: 'string', label: 'equipment', name: 'equipment', property: 'equipment' },\n { type: 'string', label: 'label', name: 'label', property: 'label' }\n ]\n}\n\n@sceneComponent('station-counts')\nexport default class StationCountsScene extends StationScene {\n render(context: CanvasRenderingContext2D) {\n const state: any = this.state\n const { left, top, width, height } = state\n const snapshot = this.snapshot\n\n if (!snapshot) {\n drawDetached(context, 'not connected', left, top, width, height)\n return\n }\n\n const today = snapshot.today\n\n drawCard(context, left, top, width, height)\n\n const y = drawHeading(context, state.label || 'today', left, top, width)\n const half = width / 2\n\n context.font = 'bold 28px sans-serif'\n context.fillStyle = STATION_COLOR.good\n context.fillText(String(today.good), left + 8, y + 4)\n\n context.fillStyle = STATION_COLOR.scrap\n context.fillText(String(today.scrap), left + half + 4, y + 4)\n\n context.font = '10px sans-serif'\n context.fillStyle = STATION_COLOR.quiet\n context.fillText('good', left + 8, y + 36)\n context.fillText('scrap', left + half + 4, y + 36)\n }\n\n get nature() {\n return NATURE\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { ComponentNature } from '@hatiolab/things-scene';
2
+ import { StationScene } from './station-scene';
3
+ export default class StationFieldScene extends StationScene {
4
+ render(context: CanvasRenderingContext2D): void;
5
+ get nature(): ComponentNature;
6
+ }
@@ -0,0 +1,89 @@
1
+ import { __decorate } from "tslib";
2
+ import { sceneComponent } from '@hatiolab/things-scene';
3
+ import { stationBindingNames } from '../station-board-binding';
4
+ import { STATION_COLOR, drawDetached, withinBox } from './station-scene-style';
5
+ import { StationScene } from './station-scene';
6
+ /**
7
+ * On a board · one thing from the context, **chosen from a list.**
8
+ *
9
+ * ── Why this exists at all ─────────────────────────────────────────────────
10
+ * Values already reach any plain text component whose id is a context name — set the id to
11
+ * `order.name` and the job order appears. That works, and it is also magic: nothing on the property
12
+ * panel says that particular id means anything, so an author has to be told, and a typo is
13
+ * indistinguishable from a name that was never offered.
14
+ *
15
+ * This component takes the same names and makes them a **dropdown**. The list comes out of
16
+ * `stationBindingNames()`, so it cannot drift from what is actually pushed. An author drops one of
17
+ * these, opens the field list, and reads what a station can tell them.
18
+ *
19
+ * The magic id still works. It is the shorter path for someone who knows the names, and drawing a
20
+ * whole line map out of these boxes would be tedious. This is the path for someone who does not.
21
+ *
22
+ * ── It shows the name while being drawn ────────────────────────────────────
23
+ * There is no runtime in the board modeller, so in edit mode this draws the field name where the
24
+ * value will be. An empty box during authoring is how a board ends up shipped with a field nobody
25
+ * ever bound.
26
+ */
27
+ const NATURE = {
28
+ mutable: false,
29
+ resizable: true,
30
+ rotatable: false,
31
+ properties: [
32
+ {
33
+ type: 'select',
34
+ label: 'field',
35
+ name: 'field',
36
+ /* Straight out of the projection — a name here that is not pushed cannot happen. */
37
+ property: { options: stationBindingNames() }
38
+ },
39
+ { type: 'string', label: 'label', name: 'label', property: 'label' },
40
+ { type: 'number', label: 'text size', name: 'valueSize', property: 'valueSize' }
41
+ ]
42
+ };
43
+ let StationFieldScene = class StationFieldScene extends StationScene {
44
+ render(context) {
45
+ const state = this.state;
46
+ const { left, top, width, height } = state;
47
+ const field = String(state.field || '');
48
+ if (!field) {
49
+ /* Nothing chosen yet. Say which box it is, so the author can find it on a busy board. */
50
+ drawDetached(context, 'pick a field', left, top, width, height);
51
+ return;
52
+ }
53
+ const bound = this.context;
54
+ withinBox(context, left, top, width, height, () => {
55
+ /*
56
+ * The caption. Falls back to the field name rather than to nothing — a bare number on a
57
+ * shop-floor screen is the thing operators most often read as the wrong quantity.
58
+ */
59
+ context.textBaseline = 'top';
60
+ context.textAlign = 'left';
61
+ context.fillStyle = STATION_COLOR.quiet;
62
+ context.font = 'bold 11px sans-serif';
63
+ context.fillText(String(state.label || field), left + 2, top + 2);
64
+ const size = Number(state.valueSize) > 0 ? Number(state.valueSize) : Math.max(14, Math.min(40, height * 0.45));
65
+ context.font = `bold ${size}px sans-serif`;
66
+ if (!bound) {
67
+ /*
68
+ * No context: the modeller, or a screen that has not attached yet. Draw the name of what
69
+ * will land here, greyed — never a zero, which would read as a real count.
70
+ */
71
+ context.fillStyle = STATION_COLOR.line;
72
+ context.fillText(field, left + 2, top + 18);
73
+ return;
74
+ }
75
+ const value = bound.values[field];
76
+ context.fillStyle = STATION_COLOR.ink;
77
+ context.fillText(value === undefined ? '' : String(value), left + 2, top + 18);
78
+ context.textBaseline = 'alphabetic';
79
+ });
80
+ }
81
+ get nature() {
82
+ return NATURE;
83
+ }
84
+ };
85
+ StationFieldScene = __decorate([
86
+ sceneComponent('station-field')
87
+ ], StationFieldScene);
88
+ export default StationFieldScene;
89
+ //# sourceMappingURL=station-field-scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-field-scene.js","sourceRoot":"","sources":["../../../client/station/scene/station-field-scene.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentNature, sceneComponent } from '@hatiolab/things-scene'\n\nimport { stationBindingNames } from '../station-board-binding'\nimport { STATION_COLOR, drawDetached, withinBox } from './station-scene-style'\nimport { StationScene } from './station-scene'\n\n/**\n * On a board · one thing from the context, **chosen from a list.**\n *\n * ── Why this exists at all ─────────────────────────────────────────────────\n * Values already reach any plain text component whose id is a context name — set the id to\n * `order.name` and the job order appears. That works, and it is also magic: nothing on the property\n * panel says that particular id means anything, so an author has to be told, and a typo is\n * indistinguishable from a name that was never offered.\n *\n * This component takes the same names and makes them a **dropdown**. The list comes out of\n * `stationBindingNames()`, so it cannot drift from what is actually pushed. An author drops one of\n * these, opens the field list, and reads what a station can tell them.\n *\n * The magic id still works. It is the shorter path for someone who knows the names, and drawing a\n * whole line map out of these boxes would be tedious. This is the path for someone who does not.\n *\n * ── It shows the name while being drawn ────────────────────────────────────\n * There is no runtime in the board modeller, so in edit mode this draws the field name where the\n * value will be. An empty box during authoring is how a board ends up shipped with a field nobody\n * ever bound.\n */\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: false,\n properties: [\n {\n type: 'select',\n label: 'field',\n name: 'field',\n /* Straight out of the projection — a name here that is not pushed cannot happen. */\n property: { options: stationBindingNames() }\n },\n { type: 'string', label: 'label', name: 'label', property: 'label' },\n { type: 'number', label: 'text size', name: 'valueSize', property: 'valueSize' }\n ]\n}\n\n@sceneComponent('station-field')\nexport default class StationFieldScene extends StationScene {\n render(context: CanvasRenderingContext2D) {\n const state: any = this.state\n const { left, top, width, height } = state\n const field = String(state.field || '')\n\n if (!field) {\n /* Nothing chosen yet. Say which box it is, so the author can find it on a busy board. */\n drawDetached(context, 'pick a field', left, top, width, height)\n return\n }\n\n const bound = this.context\n\n withinBox(context, left, top, width, height, () => {\n /*\n * The caption. Falls back to the field name rather than to nothing — a bare number on a\n * shop-floor screen is the thing operators most often read as the wrong quantity.\n */\n context.textBaseline = 'top'\n context.textAlign = 'left'\n context.fillStyle = STATION_COLOR.quiet\n context.font = 'bold 11px sans-serif'\n context.fillText(String(state.label || field), left + 2, top + 2)\n\n const size = Number(state.valueSize) > 0 ? Number(state.valueSize) : Math.max(14, Math.min(40, height * 0.45))\n\n context.font = `bold ${size}px sans-serif`\n\n if (!bound) {\n /*\n * No context: the modeller, or a screen that has not attached yet. Draw the name of what\n * will land here, greyed — never a zero, which would read as a real count.\n */\n context.fillStyle = STATION_COLOR.line\n context.fillText(field, left + 2, top + 18)\n return\n }\n\n const value = bound.values[field]\n\n context.fillStyle = STATION_COLOR.ink\n context.fillText(value === undefined ? '' : String(value), left + 2, top + 18)\n context.textBaseline = 'alphabetic'\n })\n }\n\n get nature() {\n return NATURE\n }\n}\n"]}