@songsid/agend 2.3.2-beta.8 → 2.3.2

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 (243) hide show
  1. package/README.md +6 -0
  2. package/README.zh-TW.md +6 -0
  3. package/dist/agent-cli.js +48 -0
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +6 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-whats-new.js +3 -0
  8. package/dist/agent-whats-new.js.map +1 -1
  9. package/dist/auth-api.js +2 -0
  10. package/dist/auth-api.js.map +1 -1
  11. package/dist/backend/claude-code.js +23 -1
  12. package/dist/backend/claude-code.js.map +1 -1
  13. package/dist/backend/codex-session-lookup.d.ts +15 -0
  14. package/dist/backend/codex-session-lookup.js +58 -0
  15. package/dist/backend/codex-session-lookup.js.map +1 -1
  16. package/dist/backend/codex.js +15 -4
  17. package/dist/backend/codex.js.map +1 -1
  18. package/dist/backend/kiro-classic-archive.d.ts +57 -0
  19. package/dist/backend/kiro-classic-archive.js +622 -0
  20. package/dist/backend/kiro-classic-archive.js.map +1 -0
  21. package/dist/backend/kiro-classic-links.d.ts +24 -0
  22. package/dist/backend/kiro-classic-links.js +135 -0
  23. package/dist/backend/kiro-classic-links.js.map +1 -0
  24. package/dist/backend/kiro-identity.d.ts +100 -1
  25. package/dist/backend/kiro-identity.js +262 -31
  26. package/dist/backend/kiro-identity.js.map +1 -1
  27. package/dist/backend/kiro-migration-verify.d.ts +10 -0
  28. package/dist/backend/kiro-migration-verify.js +52 -0
  29. package/dist/backend/kiro-migration-verify.js.map +1 -0
  30. package/dist/backend/kiro-reconcile-store.d.ts +27 -0
  31. package/dist/backend/kiro-reconcile-store.js +344 -0
  32. package/dist/backend/kiro-reconcile-store.js.map +1 -0
  33. package/dist/backend/kiro-v2-store-worker.js +35 -1
  34. package/dist/backend/kiro-v2-store-worker.js.map +1 -1
  35. package/dist/backend/kiro-v2-store.d.ts +31 -1
  36. package/dist/backend/kiro-v2-store.js +22 -12
  37. package/dist/backend/kiro-v2-store.js.map +1 -1
  38. package/dist/backend/kiro.d.ts +124 -1
  39. package/dist/backend/kiro.js +553 -29
  40. package/dist/backend/kiro.js.map +1 -1
  41. package/dist/backend/types.d.ts +102 -0
  42. package/dist/backend/types.js +9 -0
  43. package/dist/backend/types.js.map +1 -1
  44. package/dist/cache-service.d.ts +13 -1
  45. package/dist/cache-service.js +45 -2
  46. package/dist/cache-service.js.map +1 -1
  47. package/dist/channel/ipc-timeouts.js +3 -0
  48. package/dist/channel/ipc-timeouts.js.map +1 -1
  49. package/dist/channel/mcp-tools.js +40 -5
  50. package/dist/channel/mcp-tools.js.map +1 -1
  51. package/dist/channel/tool-router.js +2 -1
  52. package/dist/channel/tool-router.js.map +1 -1
  53. package/dist/classic-channel-manager.d.ts +6 -0
  54. package/dist/classic-channel-manager.js +25 -0
  55. package/dist/classic-channel-manager.js.map +1 -1
  56. package/dist/cli.js +50 -4
  57. package/dist/cli.js.map +1 -1
  58. package/dist/config-validator.d.ts +7 -1
  59. package/dist/config-validator.js +17 -8
  60. package/dist/config-validator.js.map +1 -1
  61. package/dist/config.js +2 -0
  62. package/dist/config.js.map +1 -1
  63. package/dist/daemon.d.ts +66 -0
  64. package/dist/daemon.js +421 -19
  65. package/dist/daemon.js.map +1 -1
  66. package/dist/dashboard-permissions.d.ts +15 -4
  67. package/dist/dashboard-permissions.js +49 -23
  68. package/dist/dashboard-permissions.js.map +1 -1
  69. package/dist/doctor.js +2 -1
  70. package/dist/doctor.js.map +1 -1
  71. package/dist/fleet-control-audit.d.ts +1 -1
  72. package/dist/fleet-control-audit.js +3 -0
  73. package/dist/fleet-control-audit.js.map +1 -1
  74. package/dist/fleet-manager.d.ts +90 -3
  75. package/dist/fleet-manager.js +569 -41
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/forged-envelope-scan.d.ts +9 -0
  78. package/dist/forged-envelope-scan.js +30 -5
  79. package/dist/forged-envelope-scan.js.map +1 -1
  80. package/dist/general-instance.d.ts +24 -0
  81. package/dist/general-instance.js +53 -0
  82. package/dist/general-instance.js.map +1 -1
  83. package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
  84. package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -3
  85. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
  86. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +34 -0
  87. package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
  88. package/dist/general-knowledge/skills/tui-effort/SKILL.md +1 -1
  89. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +32 -0
  90. package/dist/general-knowledge/skills/web-appearance/SKILL.md +32 -0
  91. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +60 -18
  92. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +60 -18
  93. package/dist/instance-config-impact.js +3 -2
  94. package/dist/instance-config-impact.js.map +1 -1
  95. package/dist/instance-lifecycle.d.ts +4 -0
  96. package/dist/instance-lifecycle.js +7 -0
  97. package/dist/instance-lifecycle.js.map +1 -1
  98. package/dist/kiro-migrate.d.ts +19 -2
  99. package/dist/kiro-migrate.js +133 -35
  100. package/dist/kiro-migrate.js.map +1 -1
  101. package/dist/kiro-reconcile.d.ts +21 -0
  102. package/dist/kiro-reconcile.js +88 -0
  103. package/dist/kiro-reconcile.js.map +1 -0
  104. package/dist/kiro-session-proof.d.ts +41 -0
  105. package/dist/kiro-session-proof.js +89 -0
  106. package/dist/kiro-session-proof.js.map +1 -0
  107. package/dist/kiro-tui-forensic.d.ts +19 -0
  108. package/dist/kiro-tui-forensic.js +124 -0
  109. package/dist/kiro-tui-forensic.js.map +1 -0
  110. package/dist/locale.js +198 -0
  111. package/dist/locale.js.map +1 -1
  112. package/dist/log-service.js +2 -2
  113. package/dist/log-service.js.map +1 -1
  114. package/dist/needs-you.d.ts +3 -1
  115. package/dist/needs-you.js +4 -2
  116. package/dist/needs-you.js.map +1 -1
  117. package/dist/office-levels.d.ts +64 -0
  118. package/dist/office-levels.js +66 -0
  119. package/dist/office-levels.js.map +1 -0
  120. package/dist/office-share-page.d.ts +3 -1
  121. package/dist/office-share-page.js +8 -3
  122. package/dist/office-share-page.js.map +1 -1
  123. package/dist/office-share-service.d.ts +6 -3
  124. package/dist/office-share-service.js +12 -8
  125. package/dist/office-share-service.js.map +1 -1
  126. package/dist/office-share.d.ts +80 -13
  127. package/dist/office-share.js +145 -25
  128. package/dist/office-share.js.map +1 -1
  129. package/dist/outbound-handlers.js +20 -1
  130. package/dist/outbound-handlers.js.map +1 -1
  131. package/dist/outbound-schemas.d.ts +47 -2
  132. package/dist/outbound-schemas.js +41 -2
  133. package/dist/outbound-schemas.js.map +1 -1
  134. package/dist/public-web-gateway.js +1 -1
  135. package/dist/public-web-gateway.js.map +1 -1
  136. package/dist/quickstart-api.d.ts +39 -1
  137. package/dist/quickstart-api.js +115 -17
  138. package/dist/quickstart-api.js.map +1 -1
  139. package/dist/remote-card-answer.d.ts +83 -0
  140. package/dist/remote-card-answer.js +208 -0
  141. package/dist/remote-card-answer.js.map +1 -0
  142. package/dist/remote-card-prompt.d.ts +38 -0
  143. package/dist/remote-card-prompt.js +86 -0
  144. package/dist/remote-card-prompt.js.map +1 -0
  145. package/dist/scheduler/task-board.d.ts +28 -6
  146. package/dist/scheduler/task-board.js +66 -11
  147. package/dist/scheduler/task-board.js.map +1 -1
  148. package/dist/service-installer.d.ts +13 -0
  149. package/dist/service-installer.js +15 -0
  150. package/dist/service-installer.js.map +1 -1
  151. package/dist/service-owner.d.ts +25 -0
  152. package/dist/service-owner.js +43 -2
  153. package/dist/service-owner.js.map +1 -1
  154. package/dist/settings-api.d.ts +2 -0
  155. package/dist/settings-api.js +18 -12
  156. package/dist/settings-api.js.map +1 -1
  157. package/dist/settings-change.js +10 -5
  158. package/dist/settings-change.js.map +1 -1
  159. package/dist/settings-effect.js +7 -4
  160. package/dist/settings-effect.js.map +1 -1
  161. package/dist/settings-instance-patch.js +1 -1
  162. package/dist/settings-instance-patch.js.map +1 -1
  163. package/dist/setup-form.d.ts +2 -2
  164. package/dist/setup-form.js +139 -93
  165. package/dist/setup-form.js.map +1 -1
  166. package/dist/setup-i18n.d.ts +25 -0
  167. package/dist/setup-i18n.js +226 -0
  168. package/dist/setup-i18n.js.map +1 -0
  169. package/dist/task-board-waker.js +7 -2
  170. package/dist/task-board-waker.js.map +1 -1
  171. package/dist/tool-permissions.js +18 -0
  172. package/dist/tool-permissions.js.map +1 -1
  173. package/dist/transcript-sources.d.ts +1 -0
  174. package/dist/transcript-sources.js +20 -3
  175. package/dist/transcript-sources.js.map +1 -1
  176. package/dist/types.d.ts +10 -0
  177. package/dist/ui/backend-signin.js +4 -3
  178. package/dist/ui/chat-store.js +3 -2
  179. package/dist/ui/chat-strings.js +2 -2
  180. package/dist/ui/fleet-office-card.js +114 -0
  181. package/dist/ui/fleet-office-editor.js +81 -9
  182. package/dist/ui/fleet-office-layout.js +87 -10
  183. package/dist/ui/fleet-office-levels.js +105 -0
  184. package/dist/ui/fleet-office-model.js +9 -1
  185. package/dist/ui/fleet-office-pack.d.ts +18 -3
  186. package/dist/ui/fleet-office-pack.js +217 -23
  187. package/dist/ui/fleet-office-skin.js +39 -3
  188. package/dist/ui/fleet-office-store.js +74 -3
  189. package/dist/ui/fleet-office-view.js +8 -0
  190. package/dist/ui/fleet-office-zones.js +102 -19
  191. package/dist/ui/fleet-office.js +323 -50
  192. package/dist/ui/fleet-org.js +2 -0
  193. package/dist/ui/fleet-task-card.js +2 -2
  194. package/dist/ui/fleet-task-common.js +30 -7
  195. package/dist/ui/fleet-task-work.js +2 -2
  196. package/dist/ui/fleet-tasks.js +10 -11
  197. package/dist/ui/office-share-dialog.js +31 -7
  198. package/dist/ui/office-share-page.js +50 -1
  199. package/dist/ui/panel-chat.js +14 -2
  200. package/dist/ui/panel-log.js +4 -4
  201. package/dist/ui/panel-needs.js +1 -1
  202. package/dist/ui/panel-settings.js +16 -8
  203. package/dist/ui/settings-discord.js +3 -2
  204. package/dist/ui/settings-guests.js +17 -7
  205. package/dist/ui/settings-model.js +17 -9
  206. package/dist/ui/settings-registry.js +1 -0
  207. package/dist/ui/settings-strings.js +16 -4
  208. package/dist/ui/settings-token.js +3 -2
  209. package/dist/ui/settings-wizard.js +25 -9
  210. package/dist/ui/shared/app-i18n.js +2 -2
  211. package/dist/ui/shared/app-stream.js +1 -1
  212. package/dist/ui/shared/app.css +74 -2
  213. package/dist/ui/shared/setup-host-text.js +107 -0
  214. package/dist/ui/structured-signin.js +89 -38
  215. package/dist/usage-rollup.d.ts +34 -3
  216. package/dist/usage-rollup.js +56 -3
  217. package/dist/usage-rollup.js.map +1 -1
  218. package/dist/web-api.d.ts +10 -1
  219. package/dist/web-api.js +207 -17
  220. package/dist/web-api.js.map +1 -1
  221. package/dist/web-auth.js +1 -1
  222. package/dist/web-auth.js.map +1 -1
  223. package/dist/web-office-acts.d.ts +5 -2
  224. package/dist/web-office-acts.js +9 -4
  225. package/dist/web-office-acts.js.map +1 -1
  226. package/dist/web-office-pack-read.d.ts +35 -0
  227. package/dist/web-office-pack-read.js +252 -0
  228. package/dist/web-office-pack-read.js.map +1 -0
  229. package/dist/web-office-pack-tools.d.ts +241 -0
  230. package/dist/web-office-pack-tools.js +785 -0
  231. package/dist/web-office-pack-tools.js.map +1 -0
  232. package/dist/web-office-skins.d.ts +68 -4
  233. package/dist/web-office-skins.js +324 -33
  234. package/dist/web-office-skins.js.map +1 -1
  235. package/dist/web-office-store.d.ts +47 -4
  236. package/dist/web-office-store.js +149 -15
  237. package/dist/web-office-store.js.map +1 -1
  238. package/dist/web-settings-patch.d.ts +3 -1
  239. package/dist/web-settings-patch.js +4 -1
  240. package/dist/web-settings-patch.js.map +1 -1
  241. package/dist/workflow-templates/default.md +1 -1
  242. package/package.json +2 -2
  243. package/templates/windows-shortcuts.ps1 +18 -17
@@ -4,6 +4,18 @@
4
4
  // overridesBetween), saved with the whole list and the rev read (PUT /ui/offices; a change made meanwhile is a 409).
5
5
  // Reset goes back to the pack's own positions. A "show coordinates" overlay draws the 1280×720 logical grid.
6
6
  //
7
+ // #2665: an item with a `night` (#2568) has a night handle too — its night strip drawn faint on the canvas (as the
8
+ // seats' figures, #2558), a moon, and a thin line to its day place. One that sleeps in place shows it on its day place
9
+ // (and it follows the item); dragged away, the item has its own night place; its Reset puts back the pack's.
10
+ //
11
+ // #2736: the mail drop — where letters from other offices land — is a handle too (an envelope). On a pack without
12
+ // one it stands, dashed, where letters land today (fleet-office-zones.js dropPoint); dragged, the office has its own.
13
+ //
14
+ // #2691: a selected seat lists the office's members under "Reserved for" (several may be picked) and "Lend when
15
+ // away": whoever it is for takes it first, and nobody else does unless it is lent while they are all elsewhere
16
+ // (fleet-office-zones.js assignSeats). Saved as the overrides' `seatFor` by area and slot; a reserved seat's handle
17
+ // is ringed.
18
+ //
7
19
  // Every handle is a button for the keyboard too: Tab to it, the arrow keys move it (Shift: by 10), Delete removes a
8
20
  // waypoint; the selected waypoint's corridors are listed with a way to join or unjoin each. Never offered to a guest,
9
21
  // the public link or a read-only session (the host refuses their saves too).
@@ -13,22 +25,27 @@ import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/as
13
25
  import { t, register } from "/assets/app-i18n.js";
14
26
  import { SidePanel } from "/assets/side-panel.js";
15
27
  import { LOGICAL, overridesBetween, own } from "./fleet-office-pack.js";
28
+ import { dropPoint } from "./fleet-office-zones.js";
16
29
 
17
30
  register("orgl", {
18
31
  en: {
19
32
  edit: "Edit layout", title: "Edit layout", coords: "Show coordinates", addWaypoint: "Add waypoint", removeWaypoint: "Remove waypoint",
20
- joinTo: "Join to", join: "Join", unjoin: "Unjoin {0}", corridors: "Corridors", none: "Nothing selected: pick a seat, an item, the door or a waypoint.",
33
+ joinTo: "Join to", join: "Join", unjoin: "Unjoin {0}", corridors: "Corridors", none: "Nothing selected: pick a seat, an item, the door, the mail drop or a waypoint.",
21
34
  reset: "Reset to the pack's layout", save: "Save layout", cancel: "Cancel", saved: "Layout saved.", hint: "Drag, or use the arrow keys (Shift: by 10).",
22
- seat: "Seat {0} in {1}", furniture: "Item {0}", door: "Door", waypoint: "Waypoint {0}", at: "{0} at {1}, {2}",
35
+ seat: "Seat {0} in {1}", furniture: "Item {0}", night: "Item {0} (night)", door: "Door", mail: "Mail drop", mailAuto: "Mail drop (where letters land now; drag to place it)", waypoint: "Waypoint {0}", at: "{0} at {1}, {2}",
36
+ nightReset: "Back to the pack's night place",
37
+ seatFor: "Seat {0} in {1}, reserved for {2}", reservedFor: "Reserved for", lend: "Lend when away", nobody: "No members to pick.",
23
38
  changed: "Someone changed the offices meanwhile. Your layout is kept: Save again puts it on the current list.",
24
39
  failed: "The layout was not saved ({0}).", rateLimited: "Too many saves. Try again in a minute.",
25
40
  stale: "This office was removed or its style changed meanwhile: this layout can no longer be saved. Cancel closes it.",
26
41
  },
27
42
  "zh-TW": {
28
43
  edit: "編輯配置", title: "編輯配置", coords: "顯示座標", addWaypoint: "新增路徑點", removeWaypoint: "移除路徑點",
29
- joinTo: "連到", join: "連接", unjoin: "取消連接 {0}", corridors: "走道", none: "尚未選取:請選座位、家具、門或路徑點。",
44
+ joinTo: "連到", join: "連接", unjoin: "取消連接 {0}", corridors: "走道", none: "尚未選取:請選座位、家具、門、信件落點或路徑點。",
30
45
  reset: "還原成 pack 的配置", save: "儲存配置", cancel: "取消", saved: "配置已儲存。", hint: "拖曳,或用方向鍵移動(Shift:一次 10)。",
31
- seat: "{1} 的座位 {0}", furniture: "家具 {0}", door: "門", waypoint: "路徑點 {0}", at: "{0},位置 {1}, {2}",
46
+ seat: "{1} 的座位 {0}", furniture: "家具 {0}", night: "家具 {0}(夜間)", door: "門", mail: "信件落點", mailAuto: "信件落點(目前信件落下的位置;拖曳即可指定)", waypoint: "路徑點 {0}", at: "{0},位置 {1}, {2}",
47
+ nightReset: "回到 pack 的夜間位置",
48
+ seatFor: "{1} 的座位 {0},保留給 {2}", reservedFor: "保留給", lend: "不在時借給別人", nobody: "沒有可選的成員。",
32
49
  changed: "有人在這期間修改了辦公室。你的配置已保留:再按一次儲存,會套用到目前的清單。",
33
50
  failed: "配置沒有儲存({0})。", rateLimited: "儲存太頻繁,請一分鐘後再試。",
34
51
  stale: "這個辦公室在這期間被移除,或它的風格已改變:這份配置無法再儲存。按取消關閉。",
@@ -39,12 +56,27 @@ const tl = (k, ...v) => t(`orgl.${k}`, ...v);
39
56
  // ── The pure part ──
40
57
 
41
58
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
42
- /** Every handle on a pack: { key, kind, label, x, y } — seats by area, furniture, the door, the waypoints. */
59
+ /** An item's night place (#2665): its own, or (in place) its day place; `own` whether it has one. */
60
+ const nightAt = (f) => (f.night.x !== null && f.night.x !== undefined && f.night.y !== null && f.night.y !== undefined ? { x: f.night.x, y: f.night.y, own: true } : { x: f.x, y: f.y, own: false });
61
+ /**
62
+ * Every handle on a pack: { key, kind, label, x, y } — seats by area (#2691: `for` and `lend` when reserved),
63
+ * furniture, each item's night place (#2665: an item with a `night`; `day` its day place, `inPlace` while it sleeps
64
+ * there), the door, the mail drop (#2736: `auto` where the pack has none — where letters land now), the waypoints.
65
+ */
43
66
  export function handlesOf(pack) {
44
67
  const out = [];
45
- for (const [name, a] of Object.entries(pack.areas)) a.seats.forEach((s, i) => out.push({ key: `seat:${name}:${i}`, kind: "seat", label: tl("seat", i + 1, name), x: s.x, y: s.y }));
68
+ for (const [name, a] of Object.entries(pack.areas)) a.seats.forEach((s, i) => out.push({ key: `seat:${name}:${i}`, kind: "seat",
69
+ label: s.for && s.for.length ? tl("seatFor", i + 1, name, s.for.join(", ")) : tl("seat", i + 1, name), x: s.x, y: s.y,
70
+ ...(s.for && s.for.length ? { for: [...s.for], lend: s.lend === true } : {}) }));
46
71
  for (const f of pack.furniture) out.push({ key: `furn:${f.id}`, kind: "furniture", label: tl("furniture", f.id), x: f.x, y: f.y });
72
+ for (const f of pack.furniture) {
73
+ if (!f.night) continue;
74
+ const n = nightAt(f);
75
+ out.push({ key: `night:${f.id}`, kind: "night", label: tl("night", f.id), x: n.x, y: n.y, day: { x: f.x, y: f.y }, inPlace: !n.own });
76
+ }
47
77
  out.push({ key: "door", kind: "door", label: tl("door"), x: pack.door.x, y: pack.door.y });
78
+ const md = pack.mailDrop || dropPoint({ areas: pack.areas, waypoints: pack.waypoints, width: LOGICAL.w, height: LOGICAL.h });
79
+ if (md) out.push({ key: "mail", kind: "mail", label: tl(pack.mailDrop ? "mail" : "mailAuto"), x: md.x, y: md.y, ...(pack.mailDrop ? {} : { auto: true }) });
48
80
  for (const w of pack.waypoints) out.push({ key: `wp:${w.id}`, kind: "waypoint", label: tl("waypoint", w.id), x: w.x, y: w.y });
49
81
  return out;
50
82
  }
@@ -56,10 +88,12 @@ export function moveHandle(pack, key, x, y) {
56
88
  const area = own(pack.areas, a), i = Number(b);
57
89
  if (!area || !area.seats[i]) return pack;
58
90
  const r = area.rect, p = { x: clamp(x, Math.ceil(r.x), Math.floor(r.x + r.w)), y: clamp(y, Math.ceil(r.y), Math.floor(r.y + r.h)) };
59
- return { ...pack, areas: { ...pack.areas, [a]: { ...area, seats: area.seats.map((s, j) => (j === i ? p : s)) } } };
91
+ return { ...pack, areas: { ...pack.areas, [a]: { ...area, seats: area.seats.map((s, j) => (j === i ? { ...s, ...p } : s)) } } };
60
92
  }
61
93
  if (kind === "furn") return { ...pack, furniture: pack.furniture.map((f) => (f.id === a ? { ...f, ...inCanvas() } : f)) };
94
+ if (kind === "night") return { ...pack, furniture: pack.furniture.map((f) => (f.id === a && f.night ? { ...f, night: { ...f.night, ...inCanvas() } } : f)) };
62
95
  if (kind === "door") return { ...pack, door: inCanvas() };
96
+ if (kind === "mail") return { ...pack, mailDrop: inCanvas() };
63
97
  if (kind === "wp") return { ...pack, waypoints: pack.waypoints.map((w) => (w.id === a ? { ...w, ...inCanvas() } : w)) };
64
98
  return pack;
65
99
  }
@@ -81,12 +115,35 @@ export function toggleEdge(pack, a, b) {
81
115
  const has = (e) => (e[0] === a && e[1] === b) || (e[0] === b && e[1] === a);
82
116
  return { ...pack, edges: pack.edges.some(has) ? pack.edges.filter((e) => !has(e)) : [...pack.edges, [a, b]] };
83
117
  }
118
+ /** #2665: item `id`'s night place back to the pack's (`base`): its own x/y, or in place again. */
119
+ export function resetNight(pack, base, id) {
120
+ const o = base.furniture.find((f) => f.id === id);
121
+ if (!o || !o.night) return pack;
122
+ return { ...pack, furniture: pack.furniture.map((f) => (f.id === id && f.night ? { ...f, night: { ...f.night, x: o.night.x, y: o.night.y } } : f)) };
123
+ }
124
+ /**
125
+ * #2691: seat `key` ("seat:<area>:<i>") reserved for `names` (in that order, none twice), lent while they are all
126
+ * elsewhere when `lend`; no names: a plain seat again.
127
+ */
128
+ export function reserveSeat(pack, key, names, lend = false) {
129
+ const [kind, a, b] = key.split(":");
130
+ const area = kind === "seat" ? own(pack.areas, a) : null, i = Number(b);
131
+ if (!area || !area.seats[i]) return pack;
132
+ const list = [...new Set(names)];
133
+ const seat = (s) => { const { for: _f, lend: _l, ...p } = s; return list.length ? { ...p, for: list, lend: lend === true } : p; };
134
+ return { ...pack, areas: { ...pack.areas, [a]: { ...area, seats: area.seats.map((s, j) => (j === i ? seat(s) : s)) } } };
135
+ }
84
136
  /** What Save keeps: the overrides between the pack and the edited layout (null: the pack's own). */
85
137
  export const layoutOverrides = (base, edited) => overridesBetween(base, edited);
86
138
 
87
139
  // ── The overlay ──
88
140
 
89
141
  const GRID = 80, LABEL = 160;
142
+ /**
143
+ * A night handle's moon (#2665): a crescent centred near (x, y). Night handles are drawn under the others: one in place
144
+ * is a wider ring round its item's square (the item stays grabbable), its moon at the ring's corner.
145
+ */
146
+ const moonPath = (x, y) => `M ${x + 1} ${y - 5} A 5 5 0 1 0 ${x + 1} ${y + 5} A 4 4 0 1 1 ${x + 1} ${y - 5} Z`;
90
147
  const KEY_STEP = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
91
148
 
92
149
  /**
@@ -95,9 +152,10 @@ const KEY_STEP = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], Arr
95
152
  * zoomed; the page sets its box through the CSSOM, never a style attribute: #1300) — a pointer is read back through
96
153
  * the overlay's own box, so a drag lands on the logical point under it at any zoom; `onChange(edited)` as anything
97
154
  * moves (the canvas redraws it); `onSave(overrides | null)` → { ok } or { ok: false, message }; `onClose()`. `stale` (a message): the
98
- * office or its style changed underneath the session — no handles, nothing to save, only Cancel.
155
+ * office or its style changed underneath the session — no handles, nothing to save, only Cancel. `members` (#2691):
156
+ * the office's instances' names, offered under a selected seat's "Reserved for".
99
157
  */
100
- export function LayoutEditor({ base, start, place = null, stale = "", dock = true, onChange, onSave, onClose }) {
158
+ export function LayoutEditor({ base, start, place = null, stale = "", dock = true, members = [], onChange, onSave, onClose }) {
101
159
  const [edited, setEdited] = useState(start);
102
160
  const [sel, setSel] = useState(null);
103
161
  const [coords, setCoords] = useState(true);
@@ -131,6 +189,14 @@ export function LayoutEditor({ base, start, place = null, stale = "", dock = tru
131
189
  };
132
190
  const add = () => { const at = selected ? { x: selected.x + 40, y: selected.y } : { x: LOGICAL.w / 2, y: LOGICAL.h / 2 }; const r = addWaypoint(edited, at.x, at.y); change(r.pack); setSel(`wp:${r.id}`); };
133
191
  const wpSel = selected && selected.kind === "waypoint" ? selected.key.slice(3) : null;
192
+ const nightSel = selected && selected.kind === "night" ? selected.key.slice(6) : null;
193
+ const seatSel = selected && selected.kind === "seat" ? selected : null;
194
+ // #2691: the members to pick from, and anyone the seat is already for who is not one now (so they can be unpicked).
195
+ const pickable = seatSel ? [...new Set([...members, ...(seatSel.for || [])])] : [];
196
+ const pickFor = (name) => {
197
+ const now = seatSel.for || [], next = now.includes(name) ? now.filter((n) => n !== name) : [...now, name];
198
+ change(reserveSeat(edited, seatSel.key, next, next.length ? seatSel.lend === true : false));
199
+ };
134
200
  const save = async () => {
135
201
  if (busy) return;
136
202
  setBusy(true); setMsg("");
@@ -167,6 +233,13 @@ export function LayoutEditor({ base, start, place = null, stale = "", dock = tru
167
233
  const controls = html`<label class="check"><input type="checkbox" class="ol-coords" checked=${coords} onChange=${() => setCoords((v) => !v)} /><span>${tl("coords")}</span></label>
168
234
  <button type="button" class="btn btn-sm ol-add" onClick=${add} disabled=${busy}>${tl("addWaypoint")}</button>
169
235
  <span class="muted ol-sel" aria-live="polite">${selected ? tl("at", selected.label, selected.x, selected.y) : tl("none")}</span>
236
+ ${seatSel ? html`<fieldset class="ol-for"><legend class="oo-pick-label">${tl("reservedFor")}</legend>
237
+ ${pickable.length ? pickable.map((n) => html`<label key=${n} class="check"><input type="checkbox" class="ol-for-pick" value=${n} checked=${(seatSel.for || []).includes(n)}
238
+ disabled=${busy} onChange=${() => pickFor(n)} /><span>${n}</span></label>`) : html`<span class="muted">${tl("nobody")}</span>`}
239
+ </fieldset>
240
+ <label class="check"><input type="checkbox" class="ol-lend" checked=${seatSel.lend === true} disabled=${busy || !(seatSel.for || []).length}
241
+ onChange=${() => change(reserveSeat(edited, seatSel.key, seatSel.for || [], !(seatSel.lend === true)))} /><span>${tl("lend")}</span></label>` : null}
242
+ ${nightSel ? html`<button type="button" class="btn btn-sm ol-night-reset" onClick=${() => change(resetNight(edited, base, nightSel))} disabled=${busy}>${tl("nightReset")}</button>` : null}
170
243
  ${wpSel ? html`<button type="button" class="btn btn-sm btn-danger ol-remove" onClick=${() => { change(removeWaypoint(edited, wpSel)); setSel(null); }} disabled=${busy}>${tl("removeWaypoint")}</button>
171
244
  <label class="oo-pick"><span class="oo-pick-label">${tl("joinTo")}</span>
172
245
  <select class="tb-select ol-join-to" value=${joinTo} onChange=${(e) => setJoinTo(e.target.value)}>
@@ -188,11 +261,15 @@ export function LayoutEditor({ base, start, place = null, stale = "", dock = tru
188
261
  ${grid}
189
262
  ${Object.values(edited.areas).map((a) => html`<rect key=${`a:${a.name}`} class="ol-area" x=${a.rect.x} y=${a.rect.y} width=${a.rect.w} height=${a.rect.h} />`)}
190
263
  ${edited.edges.map((e) => { const p = at.get(e[0]), q = at.get(e[1]); return p && q ? html`<line key=${`e:${edgeKey(e)}`} class="ol-edge" x1=${p.x} y1=${p.y} x2=${q.x} y2=${q.y} />` : null; })}
191
- ${handles.map((h) => html`<g key=${h.key} class=${`ol-handle ol-${h.kind}${sel === h.key ? " on" : ""}`} data-handle=${h.key} tabindex="0" role="button"
264
+ ${handles.filter((h) => h.kind === "night" && !h.inPlace).map((h) => html`<line key=${`nl:${h.key}`} class="ol-night-line" x1=${h.day.x} y1=${h.day.y} x2=${h.x} y2=${h.y} />`)}
265
+ ${[...handles.filter((h) => h.kind === "night"), ...handles.filter((h) => h.kind !== "night")].map((h) => html`<g key=${h.key} class=${`ol-handle ol-${h.kind}${h.for ? " reserved" : ""}${h.auto ? " auto" : ""}${sel === h.key ? " on" : ""}`} data-handle=${h.key} tabindex="0" role="button"
192
266
  aria-label=${tl("at", h.label, h.x, h.y)} aria-pressed=${sel === h.key ? "true" : "false"}
193
267
  onPointerDown=${down(h.key)} onKeyDown=${keyOn(h)} onFocus=${() => setSel(h.key)}>
194
268
  ${h.kind === "door" ? html`<rect x=${h.x - 9} y=${h.y - 9} width="18" height="18" transform=${`rotate(45 ${h.x} ${h.y})`} />`
269
+ : h.kind === "mail" ? html`<rect x=${h.x - 10} y=${h.y - 7} width="20" height="14" /><path class="ol-flap" d=${`M${h.x - 10} ${h.y - 7}L${h.x} ${h.y + 1}L${h.x + 10} ${h.y - 7}`} />`
195
270
  : h.kind === "furniture" ? html`<rect x=${h.x - 8} y=${h.y - 8} width="16" height="16" />`
271
+ : h.kind === "night" ? html`<circle cx=${h.x} cy=${h.y} r=${h.inPlace ? 15 : 8} /><path class="ol-moon" d=${h.inPlace ? moonPath(h.x + 10, h.y - 10) : moonPath(h.x, h.y)} />`
272
+ : h.kind === "seat" && h.for ? html`<circle class="ol-for-ring" cx=${h.x} cy=${h.y} r="13" /><circle cx=${h.x} cy=${h.y} r="9" />`
196
273
  : html`<circle cx=${h.x} cy=${h.y} r=${h.kind === "seat" ? 9 : 7} />`}
197
274
  ${coords && sel === h.key ? html`<text class="ol-coord" x=${h.x + 12} y=${h.y - 10}>${h.x}, ${h.y}</text>` : null}
198
275
  </g>`)}
@@ -0,0 +1,105 @@
1
+ // #2661: the Office levels on the page — what the host serves (GET /ui/office-levels, src/office-levels.ts: the level from
2
+ // each instance's lifetime work, and where the viewer may see them, the numbers behind it) put into words and colours:
3
+ // the tier a level is in, its title (the page's generic ones, or a skin pack's `levels.titles`), a title a pack pins on
4
+ // someone (`levels.fixed`, which may hide the number), the badge's text and the XP bar. Pure: no DOM, no fetch.
5
+ import { t, register } from "/assets/app-i18n.js";
6
+
7
+ register("olv", {
8
+ en: { t0: "Novice", t1: "Apprentice", t2: "Adept", t3: "Expert", t4: "Master", t5: "Legend" },
9
+ "zh-TW": { t0: "新手", t1: "學徒", t2: "熟手", t3: "專家", t4: "大師", t5: "傳奇" },
10
+ });
11
+
12
+ /** The tiers by the level each starts at: grey, green, blue, the filled blue, gold, red (#2661). */
13
+ export const TIER_FROM = [0, 10, 20, 35, 50, 100];
14
+ /** The tier (0–5) a level is in. */
15
+ export const tierOf = (level) => { let i = 0; while (i + 1 < TIER_FROM.length && level >= TIER_FROM[i + 1]) i++; return i; };
16
+
17
+ /**
18
+ * A tier's badge colours from the dashboard's tokens (no palette of its own: the status tokens and their tints, as the
19
+ * renderer reads them, readPal). { bg, fg, line }.
20
+ */
21
+ export function tierColours(tier, pal) {
22
+ switch (tier) {
23
+ case 1: return { bg: pal.okBg, fg: pal.ok, line: pal.ok };
24
+ case 2: return { bg: pal.accentDim, fg: pal.accent, line: pal.accent };
25
+ case 3: return { bg: pal.accent, fg: pal.surface, line: pal.accent };
26
+ case 4: return { bg: pal.warnBg, fg: pal.warn, line: pal.warn };
27
+ case 5: return { bg: pal.dangerBg, fg: pal.danger, line: pal.danger };
28
+ default: return { bg: pal.surface2, fg: pal.text2, line: pal.border };
29
+ }
30
+ }
31
+
32
+ /** A level as the badge writes it: up to 999 whole, then 1.1k, 12k (never wider than four characters). */
33
+ export function badgeText(level) {
34
+ if (level < 1000) return String(level);
35
+ const k = level / 1000;
36
+ return k < 10 ? `${Math.floor(k * 10) / 10}k` : `${Math.floor(k)}k`;
37
+ }
38
+
39
+ /** The title for a level: the pack's table (the last entry it has reached), else the page's tier name. */
40
+ export function titleOf(level, titles) {
41
+ if (Array.isArray(titles) && titles.length) {
42
+ let name = null;
43
+ for (const e of titles) if (level >= e.from) name = e.name;
44
+ if (name) return name;
45
+ }
46
+ return t(`olv.t${tierOf(level)}`);
47
+ }
48
+
49
+ /** What a pack pins on `name` (an instance or an NPC's id): { title, hideLevel } or null. */
50
+ export function fixedOf(name, levels) {
51
+ const f = levels && levels.fixed;
52
+ return f && Object.prototype.hasOwnProperty.call(f, name) ? f[name] : null;
53
+ }
54
+
55
+ /** Work tokens to reach a level (the host's curve: level = floor(√(work ÷ unit))). */
56
+ export const workFor = (level, unit) => level * level * unit;
57
+
58
+ /** The XP bar: { next, toNext, pct (0–1) } — the next level, the work it still needs, how far along this level is. */
59
+ export function xpOf(level, work, unit) {
60
+ const from = workFor(level, unit), to = workFor(level + 1, unit);
61
+ return { next: level + 1, toNext: Math.max(0, to - work), pct: to > from ? Math.min(1, Math.max(0, (work - from) / (to - from))) : 0 };
62
+ }
63
+
64
+ /** Tokens in short words: 950, 12.3K, 260M, 1.2B. */
65
+ export function tokens(n) {
66
+ const v = Math.max(0, n);
67
+ if (v < 1000) return String(Math.round(v));
68
+ const [d, s] = v >= 1e9 ? [1e9, "B"] : v >= 1e6 ? [1e6, "M"] : [1e3, "K"];
69
+ const x = v / d;
70
+ return `${x < 100 ? Math.floor(x * 10) / 10 : Math.floor(x)}${s}`;
71
+ }
72
+
73
+ /**
74
+ * What the host answered (GET /ui/office-levels), checked: { enabled, unit, detail, levels: name → entry } with every
75
+ * entry's numbers sane (anything odd: no levels at all).
76
+ */
77
+ export function levelsOk(v) {
78
+ const none = { enabled: false, unit: 100000, detail: false, levels: new Map() };
79
+ if (!v || typeof v !== "object" || v.enabled !== true || typeof v.levels !== "object" || !v.levels) return none;
80
+ const unit = Number.isFinite(v.unit) && v.unit > 0 ? v.unit : 100000;
81
+ const n = (x) => Number.isFinite(x) && x >= 0;
82
+ const out = new Map();
83
+ for (const [name, e] of Object.entries(v.levels)) {
84
+ if (!e || !Number.isInteger(e.level) || e.level < 0) continue;
85
+ const detail = v.detail === true && n(e.workTokens) && e.today && n(e.today.workTokens) && n(e.today.requests);
86
+ out.set(name, detail
87
+ ? { level: e.level, workTokens: e.workTokens, estimated: e.estimated === true, today: { workTokens: e.today.workTokens, requests: e.today.requests },
88
+ firstSeen: typeof e.firstSeen === "string" && /^\d{4}-\d{2}-\d{2}$/.test(e.firstSeen) ? e.firstSeen : null }
89
+ : { level: e.level });
90
+ }
91
+ return { enabled: true, unit, detail: v.detail === true, levels: out };
92
+ }
93
+
94
+ /**
95
+ * The badge a person's name tag carries: { text, tier } — or { mark: true } for a pinned title that hides the level —
96
+ * or null (levels off, or none for them).
97
+ */
98
+ export function badgeOf(name, lv, levels) {
99
+ if (!lv || !lv.enabled) return null;
100
+ const fx = fixedOf(name, levels);
101
+ if (fx && fx.hideLevel) return { mark: true, tier: 0 };
102
+ const e = lv && lv.levels.get(name);
103
+ if (!e) return null;
104
+ return { text: badgeText(e.level), tier: tierOf(e.level) };
105
+ }
@@ -74,13 +74,21 @@ export function officeRooms(view, filter) {
74
74
  return picked.map((r) => ({ ...r, nodes: r.nodes.filter((n) => pinned.has(n.name)) })).filter((r) => r.nodes.length);
75
75
  }
76
76
 
77
- /** A stored office's members: instance names, or `team:<name>` for a team's; none means everyone. */
77
+ /**
78
+ * #2752: an office kept for nobody (its style's NPCs only) stores this one member: a team with no name, which no team
79
+ * has, so a version before it finds nobody too. `[]` still means everyone.
80
+ */
81
+ export const NOBODY = "team:";
82
+ /** Who an office's members pick: "everyone" ([]), "nobody" (only NOBODY), or "picked". */
83
+ export const membersMode = (members) => (!members.length ? "everyone" : members.every((m) => m === NOBODY) ? "nobody" : "picked");
84
+ /** A stored office's members: instance names, or `team:<name>` for a team's; none means everyone, NOBODY nobody. */
78
85
  export function membersOf(office, view) {
79
86
  const every = [...view.top, ...view.teams.flatMap((tm) => tm.nodes), ...view.rest.nodes];
80
87
  const byName = new Map(every.map((n) => [n.name, n]));
81
88
  if (!office.members.length) return [...byName.values()];
82
89
  const out = new Map();
83
90
  for (const m of office.members) {
91
+ if (m === NOBODY) continue;
84
92
  const team = m.startsWith("team:") ? view.teams.find((tm) => tm.name === m.slice(5)) : null;
85
93
  for (const n of team ? team.nodes : byName.has(m) ? [byName.get(m)] : []) if (!out.has(n.name)) out.set(n.name, n);
86
94
  }
@@ -3,22 +3,35 @@
3
3
  /** `dw` × `dh`: its displayed size in logical units (w·scale × h·scale unless the manifest says otherwise). */
4
4
  export interface PackStrip { src: string; frames: number; w: number; h: number; fps: number; ax: number; ay: number; dw: number; dh: number }
5
5
  export interface PackPoint { x: number; y: number }
6
- export interface PackArea { name: string; rect: { x: number; y: number; w: number; h: number }; seats: PackPoint[] }
7
- /** `label` (#2574): its name tag, or null. */
8
- export interface PackFurniture { id: string; sprite: PackStrip; x: number; y: number; depth: number | "y"; activeWhen: string[] | null; activeSprite: PackStrip | null; label: string | null }
6
+ /** #2691: a seat reserved for `for` (lent while they are all elsewhere when `lend`); a plain seat has neither. */
7
+ export interface PackSeat extends PackPoint { for?: string[]; lend?: boolean }
8
+ export interface PackArea { name: string; rect: { x: number; y: number; w: number; h: number }; seats: PackSeat[] }
9
+ /** `label` (#2574): its name tag, or null. `activeOn`/`hides` (#2698): when it turns active, and whether it hides its people then. */
10
+ export interface PackFurniture { id: string; sprite: PackStrip; x: number; y: number; depth: number | "y"; activeWhen: string[] | null; activeSprite: PackStrip | null;
11
+ activeFor?: string[] | null; loop?: boolean; activeOn?: "state" | "seated"; hides?: boolean; label: string | null }
9
12
  export interface CheckedPack {
10
13
  format: 1; id: string; scale: number; background?: string; foreground?: string; matte?: string;
11
14
  areas: Record<string, PackArea>; stateZones: Record<string, string> | null; door: PackPoint;
15
+ /** #2736: where a letter from another office lands, or null (the waypoint nearest the middle). */
16
+ mailDrop: PackPoint | null;
12
17
  waypoints: Array<{ id: string; x: number; y: number }>; edges: Array<[string, string]>;
13
18
  furniture: PackFurniture[]; characters: Record<string, Record<string, PackStrip>>; byInstance: Record<string, string>;
14
19
  props: Record<string, PackStrip>; bubbles: Record<string, Record<string, string[]>>;
15
20
  room: { floor?: string; wall?: string }; desk: { back?: string; front?: string; lit?: string };
21
+ /** #2661: the Office levels' words — titles from each level up, and titles pinned on instances or NPCs. */
22
+ levels?: { titles?: Array<{ from: number; name: string }>; fixed?: Record<string, { title: string; hideLevel: boolean }> };
16
23
  }
17
24
  export interface LayoutOverrides {
18
25
  pack: string;
19
26
  seats?: Record<string, Array<[number, number]>>;
20
27
  furniture?: Record<string, [number, number]>;
28
+ /** #2665: an item's night place (an item with a `night` only). */
29
+ furnitureNight?: Record<string, [number, number]>;
30
+ /** #2691: a seat's reservation by area and slot ("0", "1", …), in place of the pack's; `for: []` is none. */
31
+ seatFor?: Record<string, Record<string, { for: string[]; lend: boolean }>>;
21
32
  door?: [number, number];
33
+ /** #2736: where a letter from another office lands. */
34
+ mailDrop?: [number, number];
22
35
  waypoints?: Array<{ id: string; x: number; y: number }>;
23
36
  edges?: Array<[string, string]>;
24
37
  }
@@ -35,6 +48,8 @@ export function packImages(pack: CheckedPack): string[];
35
48
  export function packStrips(pack: CheckedPack): Array<{ at: string; strip: PackStrip }>;
36
49
  export function checkLayoutOverrides(x: unknown): { ok: true; overrides: LayoutOverrides } | { ok: false; error: string };
37
50
  export function applyOverrides(pack: CheckedPack, ov: LayoutOverrides | null | undefined): { ok: true; pack: CheckedPack } | { ok: false; error: string };
51
+ export function fitNight<T extends LayoutOverrides | null | undefined>(pack: CheckedPack, ov: T): T;
52
+ export function fitSeatFor<T extends LayoutOverrides | null | undefined>(pack: CheckedPack, ov: T): T;
38
53
  export function overridesBetween(pack: CheckedPack, edited: CheckedPack): LayoutOverrides | null;
39
54
  export function poseStrip(set: Record<string, PackStrip> | null | undefined, pose: string, heading?: { dx: number; dy: number } | null): { strip: PackStrip; mirror: boolean } | null;
40
55
  export function walkStrip(f: { walk?: Record<string, PackStrip> | null; walking?: boolean; heading?: { dx: number; dy: number } | null } | null | undefined): { strip: PackStrip; mirror: boolean } | null;