@songsid/agend 2.3.0 → 2.3.2-beta.1

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 (201) hide show
  1. package/README.md +1 -1
  2. package/README.zh-TW.md +1 -1
  3. package/dist/authority/authority-state.d.ts +4 -0
  4. package/dist/authority/authority-state.js +16 -10
  5. package/dist/authority/authority-state.js.map +1 -1
  6. package/dist/authority/session-authorizations.d.ts +7 -1
  7. package/dist/authority/session-authorizations.js +6 -4
  8. package/dist/authority/session-authorizations.js.map +1 -1
  9. package/dist/authority/web-authority.d.ts +19 -6
  10. package/dist/authority/web-authority.js +22 -8
  11. package/dist/authority/web-authority.js.map +1 -1
  12. package/dist/backend/muse.d.ts +20 -2
  13. package/dist/backend/muse.js +154 -16
  14. package/dist/backend/muse.js.map +1 -1
  15. package/dist/channel/adapters/discord.d.ts +2 -0
  16. package/dist/channel/adapters/discord.js +33 -3
  17. package/dist/channel/adapters/discord.js.map +1 -1
  18. package/dist/channel/adapters/telegram.d.ts +1 -1
  19. package/dist/channel/adapters/telegram.js +3 -35
  20. package/dist/channel/adapters/telegram.js.map +1 -1
  21. package/dist/channel/mcp-tools.js +5 -5
  22. package/dist/channel/mcp-tools.js.map +1 -1
  23. package/dist/channel/types.d.ts +11 -0
  24. package/dist/cli.js +6 -3
  25. package/dist/cli.js.map +1 -1
  26. package/dist/config-validator.js +42 -5
  27. package/dist/config-validator.js.map +1 -1
  28. package/dist/daemon.d.ts +47 -3
  29. package/dist/daemon.js +380 -60
  30. package/dist/daemon.js.map +1 -1
  31. package/dist/delivery-outbox.d.ts +23 -1
  32. package/dist/delivery-outbox.js +114 -17
  33. package/dist/delivery-outbox.js.map +1 -1
  34. package/dist/doctor.d.ts +3 -0
  35. package/dist/doctor.js +9 -0
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/event-log.d.ts +5 -0
  38. package/dist/event-log.js +13 -0
  39. package/dist/event-log.js.map +1 -1
  40. package/dist/fleet-context.d.ts +1 -1
  41. package/dist/fleet-lock.d.ts +6 -0
  42. package/dist/fleet-lock.js +19 -0
  43. package/dist/fleet-lock.js.map +1 -1
  44. package/dist/fleet-manager.d.ts +174 -2
  45. package/dist/fleet-manager.js +678 -68
  46. package/dist/fleet-manager.js.map +1 -1
  47. package/dist/fleet-process-handlers.d.ts +1 -0
  48. package/dist/fleet-process-handlers.js +9 -1
  49. package/dist/fleet-process-handlers.js.map +1 -1
  50. package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +11 -0
  51. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +4 -4
  52. package/dist/general-knowledge/skills/web-appearance/SKILL.md +7 -7
  53. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +109 -57
  54. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +110 -58
  55. package/dist/guest-seen-people.d.ts +38 -0
  56. package/dist/guest-seen-people.js +78 -0
  57. package/dist/guest-seen-people.js.map +1 -0
  58. package/dist/instance-config-impact.js +4 -1
  59. package/dist/instance-config-impact.js.map +1 -1
  60. package/dist/instance-lifecycle.d.ts +2 -0
  61. package/dist/instance-lifecycle.js +27 -1
  62. package/dist/instance-lifecycle.js.map +1 -1
  63. package/dist/locale.js +62 -12
  64. package/dist/locale.js.map +1 -1
  65. package/dist/log-service.js +1 -0
  66. package/dist/log-service.js.map +1 -1
  67. package/dist/mac-auto-restore.d.ts +117 -0
  68. package/dist/mac-auto-restore.js +294 -0
  69. package/dist/mac-auto-restore.js.map +1 -0
  70. package/dist/needs-you.js +1 -0
  71. package/dist/needs-you.js.map +1 -1
  72. package/dist/outbound-handlers.d.ts +2 -2
  73. package/dist/outbound-handlers.js +5 -5
  74. package/dist/outbound-handlers.js.map +1 -1
  75. package/dist/outbound-schemas.d.ts +2 -0
  76. package/dist/outbound-schemas.js +2 -0
  77. package/dist/outbound-schemas.js.map +1 -1
  78. package/dist/public-web-gateway.js +9 -1
  79. package/dist/public-web-gateway.js.map +1 -1
  80. package/dist/search/search-index.d.ts +10 -2
  81. package/dist/search/search-index.js +17 -4
  82. package/dist/search/search-index.js.map +1 -1
  83. package/dist/service-installer.d.ts +23 -1
  84. package/dist/service-installer.js +40 -11
  85. package/dist/service-installer.js.map +1 -1
  86. package/dist/session-oom-stop.d.ts +45 -0
  87. package/dist/session-oom-stop.js +259 -0
  88. package/dist/session-oom-stop.js.map +1 -0
  89. package/dist/session-oom.d.ts +13 -0
  90. package/dist/session-oom.js +119 -7
  91. package/dist/session-oom.js.map +1 -1
  92. package/dist/settings-api.d.ts +27 -1
  93. package/dist/settings-api.js +108 -2
  94. package/dist/settings-api.js.map +1 -1
  95. package/dist/settings-change.js +37 -3
  96. package/dist/settings-change.js.map +1 -1
  97. package/dist/settings-effect.js +5 -2
  98. package/dist/settings-effect.js.map +1 -1
  99. package/dist/spawn-gate.d.ts +2 -0
  100. package/dist/spawn-gate.js +17 -4
  101. package/dist/spawn-gate.js.map +1 -1
  102. package/dist/start-attempt.d.ts +15 -1
  103. package/dist/start-attempt.js +35 -5
  104. package/dist/start-attempt.js.map +1 -1
  105. package/dist/start-failure-chat.d.ts +41 -0
  106. package/dist/start-failure-chat.js +159 -0
  107. package/dist/start-failure-chat.js.map +1 -0
  108. package/dist/start-failure-notice.d.ts +4 -0
  109. package/dist/start-failure-notice.js +8 -0
  110. package/dist/start-failure-notice.js.map +1 -1
  111. package/dist/start-failure.d.ts +49 -0
  112. package/dist/start-failure.js +141 -1
  113. package/dist/start-failure.js.map +1 -1
  114. package/dist/telegram-api-root.d.ts +1 -0
  115. package/dist/telegram-api-root.js +40 -0
  116. package/dist/telegram-api-root.js.map +1 -0
  117. package/dist/turn-reply-guard.d.ts +25 -0
  118. package/dist/turn-reply-guard.js +111 -12
  119. package/dist/turn-reply-guard.js.map +1 -1
  120. package/dist/types.d.ts +21 -3
  121. package/dist/ui/backend-signin.js +5 -2
  122. package/dist/ui/chat-ambient.js +1 -1
  123. package/dist/ui/chat-effects.js +4 -4
  124. package/dist/ui/chat-render.js +5 -2
  125. package/dist/ui/chat-strings.js +2 -2
  126. package/dist/ui/chat-thread.js +2 -2
  127. package/dist/ui/first-run.js +2 -2
  128. package/dist/ui/fleet-canvas-view.js +114 -0
  129. package/dist/ui/fleet-canvas.js +15 -77
  130. package/dist/ui/fleet-office-editor.js +80 -5
  131. package/dist/ui/fleet-office-layout.js +197 -0
  132. package/dist/ui/fleet-office-model.js +6 -3
  133. package/dist/ui/fleet-office-pack.d.ts +39 -0
  134. package/dist/ui/fleet-office-pack.js +240 -29
  135. package/dist/ui/fleet-office-skin.js +130 -21
  136. package/dist/ui/fleet-office-store.js +75 -4
  137. package/dist/ui/fleet-office-view.js +154 -0
  138. package/dist/ui/fleet-office-zones.js +95 -33
  139. package/dist/ui/fleet-office.js +530 -94
  140. package/dist/ui/fleet-usage.js +7 -6
  141. package/dist/ui/panel-chat.js +20 -7
  142. package/dist/ui/panel-details.js +2 -2
  143. package/dist/ui/panel-fleet.js +11 -77
  144. package/dist/ui/panel-log.js +5 -5
  145. package/dist/ui/panel-settings.js +586 -164
  146. package/dist/ui/search-dialog.js +26 -7
  147. package/dist/ui/settings-appearance.js +82 -29
  148. package/dist/ui/settings-apply.js +42 -7
  149. package/dist/ui/settings-dialogs.js +68 -22
  150. package/dist/ui/settings-draft.js +51 -0
  151. package/dist/ui/settings-guests.js +244 -78
  152. package/dist/ui/settings-learn.js +2 -2
  153. package/dist/ui/settings-model.js +2 -0
  154. package/dist/ui/settings-registry.js +142 -0
  155. package/dist/ui/settings-strings.js +108 -44
  156. package/dist/ui/settings-web-authority.js +34 -23
  157. package/dist/ui/shared/app-i18n.js +14 -0
  158. package/dist/ui/shared/app-nav.js +10 -5
  159. package/dist/ui/shared/app-prefs.js +80 -2
  160. package/dist/ui/shared/app-route.js +39 -5
  161. package/dist/ui/shared/app-shell.js +80 -8
  162. package/dist/ui/shared/app-stream.js +2 -1
  163. package/dist/ui/shared/app.css +251 -20
  164. package/dist/ui/shared/app.js +3 -0
  165. package/dist/ui/shared/effect-stock-trust.js +1 -1
  166. package/dist/ui/shared/identity-color.js +4 -4
  167. package/dist/ui/shared/signin.js +2 -2
  168. package/dist/ui/shared/ui-dialog.js +6 -1
  169. package/dist/ui/shared/ui-icons.js +2 -0
  170. package/dist/ui/shared/usage-dialog.js +21 -11
  171. package/dist/ui/shared/view-strings.js +2 -0
  172. package/dist/ui/shared/web-authorize.js +13 -12
  173. package/dist/web-api.d.ts +9 -3
  174. package/dist/web-api.js +161 -6
  175. package/dist/web-api.js.map +1 -1
  176. package/dist/web-auth.js +7 -1
  177. package/dist/web-auth.js.map +1 -1
  178. package/dist/web-experimental.d.ts +1 -1
  179. package/dist/web-experimental.js +1 -1
  180. package/dist/web-experimental.js.map +1 -1
  181. package/dist/web-mod-agent.js +2 -2
  182. package/dist/web-mod-agent.js.map +1 -1
  183. package/dist/web-office-skins.d.ts +108 -0
  184. package/dist/web-office-skins.js +298 -0
  185. package/dist/web-office-skins.js.map +1 -0
  186. package/dist/web-office-store.d.ts +8 -2
  187. package/dist/web-office-store.js +37 -7
  188. package/dist/web-office-store.js.map +1 -1
  189. package/dist/web-settings-patch.d.ts +6 -0
  190. package/dist/web-settings-patch.js +61 -4
  191. package/dist/web-settings-patch.js.map +1 -1
  192. package/dist/web-shell-routes.d.ts +4 -2
  193. package/dist/web-shell-routes.js +17 -7
  194. package/dist/web-shell-routes.js.map +1 -1
  195. package/dist/working-recovery.d.ts +104 -0
  196. package/dist/working-recovery.js +263 -0
  197. package/dist/working-recovery.js.map +1 -0
  198. package/dist/working-state-journal.d.ts +70 -0
  199. package/dist/working-state-journal.js +106 -0
  200. package/dist/working-state-journal.js.map +1 -0
  201. package/package.json +1 -1
@@ -0,0 +1,197 @@
1
+ // Fleet → Org chart → Office → Edit layout (#2513): an admin moves things on a stored office's zones skin — its seats,
2
+ // furniture, the door and the walkable graph's waypoints — and adds or removes waypoints and the corridors between
3
+ // them. The pack stays as it was uploaded: what differs from it is kept as the office's `overrides` (fleet-office-pack.js
4
+ // overridesBetween), saved with the whole list and the rev read (PUT /ui/offices; a change made meanwhile is a 409).
5
+ // Reset goes back to the pack's own positions. A "show coordinates" overlay draws the 1280×720 logical grid.
6
+ //
7
+ // Every handle is a button for the keyboard too: Tab to it, the arrow keys move it (Shift: by 10), Delete removes a
8
+ // waypoint; the selected waypoint's corridors are listed with a way to join or unjoin each. Never offered to a guest,
9
+ // the public link or a read-only session (the host refuses their saves too).
10
+ //
11
+ // The pure part (handles, moving, adding and removing) is exported for tests; LayoutEditor is the overlay.
12
+ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
13
+ import { t, register } from "/assets/app-i18n.js";
14
+ import { LOGICAL, overridesBetween, own } from "./fleet-office-pack.js";
15
+
16
+ register("orgl", {
17
+ en: {
18
+ edit: "Edit layout", title: "Edit layout", coords: "Show coordinates", addWaypoint: "Add waypoint", removeWaypoint: "Remove waypoint",
19
+ joinTo: "Join to", join: "Join", unjoin: "Unjoin {0}", corridors: "Corridors", none: "Nothing selected: pick a seat, an item, the door or a waypoint.",
20
+ 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).",
21
+ seat: "Seat {0} in {1}", furniture: "Item {0}", door: "Door", waypoint: "Waypoint {0}", at: "{0} at {1}, {2}",
22
+ changed: "Someone changed the offices meanwhile. Your layout is kept: Save again puts it on the current list.",
23
+ failed: "The layout was not saved ({0}).", rateLimited: "Too many saves. Try again in a minute.",
24
+ stale: "This office was removed or its style changed meanwhile: this layout can no longer be saved. Cancel closes it.",
25
+ },
26
+ "zh-TW": {
27
+ edit: "編輯配置", title: "編輯配置", coords: "顯示座標", addWaypoint: "新增路徑點", removeWaypoint: "移除路徑點",
28
+ joinTo: "連到", join: "連接", unjoin: "取消連接 {0}", corridors: "走道", none: "尚未選取:請選座位、家具、門或路徑點。",
29
+ reset: "還原成 pack 的配置", save: "儲存配置", cancel: "取消", saved: "配置已儲存。", hint: "拖曳,或用方向鍵移動(Shift:一次 10)。",
30
+ seat: "{1} 的座位 {0}", furniture: "家具 {0}", door: "門", waypoint: "路徑點 {0}", at: "{0},位置 {1}, {2}",
31
+ changed: "有人在這期間修改了辦公室。你的配置已保留:再按一次儲存,會套用到目前的清單。",
32
+ failed: "配置沒有儲存({0})。", rateLimited: "儲存太頻繁,請一分鐘後再試。",
33
+ stale: "這個辦公室在這期間被移除,或它的風格已改變:這份配置無法再儲存。按取消關閉。",
34
+ },
35
+ });
36
+ const tl = (k, ...v) => t(`orgl.${k}`, ...v);
37
+
38
+ // ── The pure part ──
39
+
40
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
41
+ /** Every handle on a pack: { key, kind, label, x, y } — seats by area, furniture, the door, the waypoints. */
42
+ export function handlesOf(pack) {
43
+ const out = [];
44
+ 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 }));
45
+ 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 });
46
+ out.push({ key: "door", kind: "door", label: tl("door"), x: pack.door.x, y: pack.door.y });
47
+ 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 });
48
+ return out;
49
+ }
50
+ /** The pack with handle `key` at (x, y), rounded and kept in range: a seat inside its area, anything else on the canvas. */
51
+ export function moveHandle(pack, key, x, y) {
52
+ const [kind, a, b] = key.split(":");
53
+ const inCanvas = () => ({ x: clamp(x, 0, LOGICAL.w), y: clamp(y, 0, LOGICAL.h) });
54
+ if (kind === "seat") {
55
+ const area = own(pack.areas, a), i = Number(b);
56
+ if (!area || !area.seats[i]) return pack;
57
+ 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)) };
58
+ return { ...pack, areas: { ...pack.areas, [a]: { ...area, seats: area.seats.map((s, j) => (j === i ? p : s)) } } };
59
+ }
60
+ if (kind === "furn") return { ...pack, furniture: pack.furniture.map((f) => (f.id === a ? { ...f, ...inCanvas() } : f)) };
61
+ if (kind === "door") return { ...pack, door: inCanvas() };
62
+ if (kind === "wp") return { ...pack, waypoints: pack.waypoints.map((w) => (w.id === a ? { ...w, ...inCanvas() } : w)) };
63
+ return pack;
64
+ }
65
+ /** A new waypoint at (x, y), named wp-<n> (a name no other waypoint has); returns { pack, id }. */
66
+ export function addWaypoint(pack, x = LOGICAL.w / 2, y = LOGICAL.h / 2) {
67
+ const ids = new Set(pack.waypoints.map((w) => w.id));
68
+ let n = pack.waypoints.length + 1;
69
+ while (ids.has(`wp-${n}`)) n++;
70
+ const id = `wp-${n}`;
71
+ return { pack: { ...pack, waypoints: [...pack.waypoints, { id, x: clamp(x, 0, LOGICAL.w), y: clamp(y, 0, LOGICAL.h) }] }, id };
72
+ }
73
+ /** The pack without waypoint `id` and its corridors. */
74
+ export function removeWaypoint(pack, id) {
75
+ return { ...pack, waypoints: pack.waypoints.filter((w) => w.id !== id), edges: pack.edges.filter((e) => e[0] !== id && e[1] !== id) };
76
+ }
77
+ /** A corridor between `a` and `b` added, or removed when there is one (either way round). */
78
+ export function toggleEdge(pack, a, b) {
79
+ if (a === b || !pack.waypoints.some((w) => w.id === a) || !pack.waypoints.some((w) => w.id === b)) return pack;
80
+ const has = (e) => (e[0] === a && e[1] === b) || (e[0] === b && e[1] === a);
81
+ return { ...pack, edges: pack.edges.some(has) ? pack.edges.filter((e) => !has(e)) : [...pack.edges, [a, b]] };
82
+ }
83
+ /** What Save keeps: the overrides between the pack and the edited layout (null: the pack's own). */
84
+ export const layoutOverrides = (base, edited) => overridesBetween(base, edited);
85
+
86
+ // ── The overlay ──
87
+
88
+ const GRID = 80, LABEL = 160;
89
+ const KEY_STEP = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
90
+
91
+ /**
92
+ * The editor over the canvas: `base` the pack as uploaded; `start` the layout shown when it opens (the base with the
93
+ * office's overrides on it); `place(el)` puts the overlay over the scene wherever the view has it (#2540: panned and
94
+ * zoomed; the page sets its box through the CSSOM, never a style attribute: #1300) — a pointer is read back through
95
+ * the overlay's own box, so a drag lands on the logical point under it at any zoom; `onChange(edited)` as anything
96
+ * moves (the canvas redraws it); `onSave(overrides | null)` → { ok } or { ok: false, message }; `onClose()`. `stale` (a message): the
97
+ * office or its style changed underneath the session — no handles, nothing to save, only Cancel.
98
+ */
99
+ export function LayoutEditor({ base, start, place = null, stale = "", onChange, onSave, onClose }) {
100
+ const [edited, setEdited] = useState(start);
101
+ const [sel, setSel] = useState(null);
102
+ const [coords, setCoords] = useState(true);
103
+ const [busy, setBusy] = useState(false);
104
+ const [msg, setMsg] = useState("");
105
+ const [joinTo, setJoinTo] = useState("");
106
+ const svg = useRef(null), drag = useRef(null), overlay = useRef(null);
107
+ useLayoutEffect(() => { if (place) place(overlay.current); });
108
+ const change = (next) => { setEdited(next); onChange(next); };
109
+ useEffect(() => { onChange(start); }, []);
110
+ const handles = handlesOf(edited);
111
+ const selected = sel ? handles.find((h) => h.key === sel) || null : null;
112
+ const toLogical = (e) => {
113
+ const r = svg.current && svg.current.getBoundingClientRect ? svg.current.getBoundingClientRect() : null;
114
+ if (!r || !r.width || !r.height) return null;
115
+ return { x: ((e.clientX - r.left) / r.width) * LOGICAL.w, y: ((e.clientY - r.top) / r.height) * LOGICAL.h };
116
+ };
117
+ const down = (key) => (e) => {
118
+ if (busy) return;
119
+ setSel(key); drag.current = key;
120
+ try { e.currentTarget?.setPointerCapture?.(e.pointerId); } catch { /* not captured: moves still reach the svg */ }
121
+ };
122
+ const move = (e) => { if (!drag.current) return; const p = toLogical(e); if (p) change(moveHandle(edited, drag.current, p.x, p.y)); };
123
+ const up = () => { drag.current = null; };
124
+ const keyOn = (h) => (e) => {
125
+ if (busy) return;
126
+ const d = KEY_STEP[e.key];
127
+ if (d) { e.preventDefault(); const k = e.shiftKey ? 10 : 1; setSel(h.key); change(moveHandle(edited, h.key, h.x + d[0] * k, h.y + d[1] * k)); return; }
128
+ if ((e.key === "Delete" || e.key === "Backspace") && h.kind === "waypoint") { e.preventDefault(); change(removeWaypoint(edited, h.key.slice(3))); setSel(null); }
129
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSel(h.key); }
130
+ };
131
+ 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}`); };
132
+ const wpSel = selected && selected.kind === "waypoint" ? selected.key.slice(3) : null;
133
+ const save = async () => {
134
+ if (busy) return;
135
+ setBusy(true); setMsg("");
136
+ try {
137
+ const r = await onSave(layoutOverrides(base, edited));
138
+ if (r && r.ok) { onClose(); return; }
139
+ setMsg(r && r.message ? r.message : tl("failed", "?"));
140
+ } finally { setBusy(false); }
141
+ };
142
+ const reset = () => { change(base); setSel(null); setMsg(""); };
143
+ const grid = [];
144
+ if (coords) {
145
+ for (let x = GRID; x < LOGICAL.w; x += GRID) grid.push(html`<line key=${`gx${x}`} class="ol-grid" x1=${x} y1="0" x2=${x} y2=${LOGICAL.h} />`);
146
+ for (let y = GRID; y < LOGICAL.h; y += GRID) grid.push(html`<line key=${`gy${y}`} class="ol-grid" x1="0" y1=${y} x2=${LOGICAL.w} y2=${y} />`);
147
+ for (let x = 0; x < LOGICAL.w; x += LABEL) grid.push(html`<text key=${`lx${x}`} class="ol-label" x=${x + 3} y="14">${x}</text>`);
148
+ for (let y = LABEL; y < LOGICAL.h; y += LABEL) grid.push(html`<text key=${`ly${y}`} class="ol-label" x="3" y=${y - 3}>${y}</text>`);
149
+ }
150
+ const at = new Map(edited.waypoints.map((w) => [w.id, w]));
151
+ const edgeKey = (e) => `${e[0]}\u0000${e[1]}`;
152
+ if (stale) {
153
+ return html`<div class="ol" role="group" aria-label=${tl("title")}><div class="ol-bar">
154
+ <p class="note ol-msg ol-stale" role="status">${stale}</p>
155
+ <span class="ol-actions"><button type="button" class="btn btn-sm ol-cancel" onClick=${onClose} disabled=${busy}>${tl("cancel")}</button></span>
156
+ </div></div>`;
157
+ }
158
+ return html`<div class="ol" role="group" aria-label=${tl("title")}>
159
+ <div class="ol-overlay" ref=${overlay}>
160
+ <svg ref=${svg} class="ol-svg" viewBox=${`0 0 ${LOGICAL.w} ${LOGICAL.h}`} width="100%" height="100%"
161
+ onPointerMove=${move} onPointerUp=${up} onPointerCancel=${up} onPointerLeave=${up}>
162
+ ${grid}
163
+ ${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} />`)}
164
+ ${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; })}
165
+ ${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"
166
+ aria-label=${tl("at", h.label, h.x, h.y)} aria-pressed=${sel === h.key ? "true" : "false"}
167
+ onPointerDown=${down(h.key)} onKeyDown=${keyOn(h)} onFocus=${() => setSel(h.key)}>
168
+ ${h.kind === "door" ? html`<rect x=${h.x - 9} y=${h.y - 9} width="18" height="18" transform=${`rotate(45 ${h.x} ${h.y})`} />`
169
+ : h.kind === "furniture" ? html`<rect x=${h.x - 8} y=${h.y - 8} width="16" height="16" />`
170
+ : html`<circle cx=${h.x} cy=${h.y} r=${h.kind === "seat" ? 9 : 7} />`}
171
+ ${coords && sel === h.key ? html`<text class="ol-coord" x=${h.x + 12} y=${h.y - 10}>${h.x}, ${h.y}</text>` : null}
172
+ </g>`)}
173
+ </svg>
174
+ </div>
175
+ <div class="ol-bar">
176
+ <label class="check"><input type="checkbox" class="ol-coords" checked=${coords} onChange=${() => setCoords((v) => !v)} /><span>${tl("coords")}</span></label>
177
+ <button type="button" class="btn btn-sm ol-add" onClick=${add} disabled=${busy}>${tl("addWaypoint")}</button>
178
+ <span class="muted ol-sel" aria-live="polite">${selected ? tl("at", selected.label, selected.x, selected.y) : tl("none")}</span>
179
+ ${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>
180
+ <label class="oo-pick"><span class="oo-pick-label">${tl("joinTo")}</span>
181
+ <select class="tb-select ol-join-to" value=${joinTo} onChange=${(e) => setJoinTo(e.target.value)}>
182
+ <option value="">—</option>
183
+ ${edited.waypoints.filter((w) => w.id !== wpSel).map((w) => html`<option key=${w.id} value=${w.id}>${w.id}</option>`)}
184
+ </select></label>
185
+ <button type="button" class="btn btn-sm ol-join" disabled=${busy || !joinTo || joinTo === wpSel} onClick=${() => { change(toggleEdge(edited, wpSel, joinTo)); setJoinTo(""); }}>${tl("join")}</button>
186
+ <span class="ol-corridors">${edited.edges.filter((e) => e[0] === wpSel || e[1] === wpSel).map((e) => { const other = e[0] === wpSel ? e[1] : e[0];
187
+ return html`<button key=${edgeKey(e)} type="button" class="btn btn-sm ol-unjoin" data-other=${other} disabled=${busy} onClick=${() => change(toggleEdge(edited, wpSel, other))}>${tl("unjoin", other)}</button>`; })}</span>` : null}
188
+ <span class="ol-actions">
189
+ <button type="button" class="btn btn-sm ol-reset" onClick=${reset} disabled=${busy}>${tl("reset")}</button>
190
+ <button type="button" class="btn btn-sm ol-cancel" onClick=${onClose} disabled=${busy}>${tl("cancel")}</button>
191
+ <button type="button" class="btn btn-sm btn-primary ol-save" onClick=${save} disabled=${busy}>${tl("save")}</button>
192
+ </span>
193
+ <span class="muted oo-note">${tl("hint")}</span>
194
+ ${msg ? html`<p class="note ol-msg" role="status">${msg}</p>` : null}
195
+ </div>
196
+ </div>`;
197
+ }
@@ -6,14 +6,16 @@
6
6
  // office the page makes itself (AUTO_ID, "the whole fleet", built from the teams as the canvas is), whose member
7
7
  // filter is the team / people picker; any other office is the host's (GET /ui/offices, fleet-office-store.js),
8
8
  // checked again here.
9
- // TODO(office editor): adding and renaming offices, assigning people, dragging seats and drawing paths is a later
10
- // step; it writes the same model through the store's saveOffices().
9
+ // The office editor (fleet-office-editor.js) adds, renames and deletes offices and picks who is in them; the layout
10
+ // editor (fleet-office-layout.js, #2513) moves things on a zones skin and keeps them as the office's `overrides`
11
+ // (fleet-office-pack.js) — both write the same model through the store's saveOffices().
11
12
  // - The auto layout: a lobby (General and the instances in no team), then a room per team in fleet.yaml's order, in a
12
13
  // grid that wraps at the stage's width (a phone gets narrower rooms); a desk per member; a corridor graph of waypoints
13
14
  // (each desk's aisle, a spine down each room's left padding, its door, a corridor under each row of rooms and one down
14
15
  // the left margin) that traffic walks along (bfsPath).
15
16
  // - Poses come from the org chart's states (fleet-org.js ORG_STATES / orgState); nothing here invents a status.
16
17
  import { hue } from "./fleet-traffic.js";
18
+ import { checkLayoutOverrides } from "./fleet-office-pack.js";
17
19
 
18
20
  // ── Poses: the org chart's states, as a character at a desk ──
19
21
 
@@ -115,7 +117,8 @@ export function officeOk(x) {
115
117
  if (!x || typeof x !== "object" || typeof x.id !== "string" || !ID.test(x.id) || x.id === AUTO_ID) return null;
116
118
  const name = typeof x.name === "string" && x.name.trim() ? x.name.trim().slice(0, 60) : x.id;
117
119
  const skin = typeof x.skin === "string" && ID.test(x.skin) ? x.skin : "pixel";
118
- return { id: x.id, name, skin, members: nameList(x.members), layout: layoutOk(x.layout) };
120
+ const ov = x.overrides === undefined ? null : checkLayoutOverrides(x.overrides);
121
+ return { id: x.id, name, skin, members: nameList(x.members), layout: layoutOk(x.layout), ...(ov && ov.ok ? { overrides: ov.overrides } : {}) };
119
122
  }
120
123
  /** A stored list of offices: the valid ones, ids unique, at most MAX_OFFICES. */
121
124
  export function officesOk(list) {
@@ -0,0 +1,39 @@
1
+ // Types for fleet-office-pack.js: the page's module, which the host imports too (web-office-skins.ts,
2
+ // web-office-store.ts), so both check a skin pack and an office's layout overrides by the same rules.
3
+ /** `dw` × `dh`: its displayed size in logical units (w·scale × h·scale unless the manifest says otherwise). */
4
+ export interface PackStrip { src: string; frames: number; w: number; h: number; fps: number; ax: number; ay: number; dw: number; dh: number }
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
+ export interface PackFurniture { id: string; sprite: PackStrip; x: number; y: number; depth: number | "y"; activeWhen: string[] | null; activeSprite: PackStrip | null }
8
+ export interface CheckedPack {
9
+ format: 1; id: string; scale: number; background?: string; foreground?: string; matte?: string;
10
+ areas: Record<string, PackArea>; stateZones: Record<string, string> | null; door: PackPoint;
11
+ waypoints: Array<{ id: string; x: number; y: number }>; edges: Array<[string, string]>;
12
+ furniture: PackFurniture[]; characters: Record<string, Record<string, PackStrip>>; byInstance: Record<string, string>;
13
+ props: Record<string, PackStrip>; bubbles: Record<string, Record<string, string[]>>;
14
+ room: { floor?: string; wall?: string }; desk: { back?: string; front?: string; lit?: string };
15
+ }
16
+ export interface LayoutOverrides {
17
+ pack: string;
18
+ seats?: Record<string, Array<[number, number]>>;
19
+ furniture?: Record<string, [number, number]>;
20
+ door?: [number, number];
21
+ waypoints?: Array<{ id: string; x: number; y: number }>;
22
+ edges?: Array<[string, string]>;
23
+ }
24
+ export const LOGICAL: { w: number; h: number };
25
+ export const CHAR_DEPTH: number;
26
+ export const PACK_STATES: string[];
27
+ export function own<T = unknown>(o: unknown, k: unknown): T | undefined;
28
+ export function inherited(k: unknown): boolean;
29
+ export function matteOk(v: unknown): v is string;
30
+ export function checkStrip(v: unknown, at?: string, scale?: number): PackStrip;
31
+ export function checkSkinPack(x: unknown): { ok: true; pack: CheckedPack } | { ok: false; error: string };
32
+ export function skinPackOk(x: unknown): CheckedPack | null;
33
+ export function packImages(pack: CheckedPack): string[];
34
+ export function packStrips(pack: CheckedPack): Array<{ at: string; strip: PackStrip }>;
35
+ export function checkLayoutOverrides(x: unknown): { ok: true; overrides: LayoutOverrides } | { ok: false; error: string };
36
+ export function applyOverrides(pack: CheckedPack, ov: LayoutOverrides | null | undefined): { ok: true; pack: CheckedPack } | { ok: false; error: string };
37
+ export function overridesBetween(pack: CheckedPack, edited: CheckedPack): LayoutOverrides | null;
38
+ export function poseStrip(set: Record<string, PackStrip> | null | undefined, pose: string, heading?: { dx: number; dy: number } | null): { strip: PackStrip; mirror: boolean } | null;
39
+ export function stripBox(st: PackStrip, x: number, y: number, mirror?: boolean): { x: number; y: number; w: number; h: number };