pi-roundtable-web 0.28.0 → 0.29.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.
- package/CHANGELOG.md +46 -0
- package/README.md +4 -4
- package/dist/chunk-account-66e6ezdq.js +1 -0
- package/dist/{chunk-action-menu-qpp9bdxh.js → chunk-action-menu-ap5xn7bf.js} +1 -1
- package/dist/chunk-agent-sapt5qhn.js +1 -0
- package/dist/chunk-arranged-rooms-n6bk696f.js +1 -0
- package/dist/chunk-attachments-p5v9gqyt.js +102 -0
- package/dist/chunk-bulk-actions-aze4ewc4.js +1 -0
- package/dist/chunk-character-creator-m7tw20ef.js +225 -0
- package/dist/{chunk-codeeditor-jgj32247.js → chunk-codeeditor-yy6pq8mv.js} +2 -2
- package/dist/chunk-confirm-0cajsfhk.js +1 -0
- package/dist/chunk-connectors-tw0tbm21.js +37 -0
- package/dist/chunk-conversations-wkm23m5r.js +1 -0
- package/dist/chunk-council-sheet-mz7s23nq.js +2 -0
- package/dist/chunk-council-view-zgvzxtgn.js +1 -0
- package/dist/{chunk-drag-04qfef58.js → chunk-drag-d1bpy8w6.js} +1 -1
- package/dist/{chunk-entry-mws704mc.js → chunk-entry-7zr5b1fp.js} +1 -1
- package/dist/chunk-folder-list-bs4eh3g9.js +1 -0
- package/dist/chunk-folds-eez1q41c.js +1 -0
- package/dist/{chunk-gate-vxetvn8t.js → chunk-gate-dt91egcd.js} +1 -1
- package/dist/chunk-host-icons-nfe2t8tk.js +1 -0
- package/dist/chunk-host-picture-4rtswxwa.js +1 -0
- package/dist/chunk-host-state-1vxzmmq6.js +1 -0
- package/dist/chunk-host-ui-x38bf0et.js +1 -0
- package/dist/{chunk-index-zj0gjcvr.js → chunk-index-0h99xcs3.js} +2 -2
- package/dist/{chunk-index-e2gy763y.js → chunk-index-1kb8tv4v.js} +2 -2
- package/dist/chunk-index-24cggw6k.js +50 -0
- package/dist/chunk-index-2g8xtwqg.js +2 -0
- package/dist/chunk-index-3n38xe7m.js +2 -0
- package/dist/{chunk-index-n0cmnwd2.js → chunk-index-3tksdmx7.js} +2 -2
- package/dist/{chunk-index-2822z600.js → chunk-index-4a1tdadd.js} +3 -3
- package/dist/{chunk-index-2syyyjnq.js → chunk-index-53fwc7sc.js} +2 -2
- package/dist/chunk-index-55ps4x5n.js +2 -0
- package/dist/{chunk-index-q5fyrtx4.js → chunk-index-5j1v2q7w.js} +1 -1
- package/dist/chunk-index-5w6tb7p0.js +2 -0
- package/dist/chunk-index-6btdp13p.js +2 -0
- package/dist/chunk-index-6m7dwsmv.js +1 -0
- package/dist/{chunk-index-20xgx0k2.js → chunk-index-6rdywten.js} +2 -2
- package/dist/{chunk-index-3gfbhmpb.js → chunk-index-6s2pqwna.js} +2 -2
- package/dist/{chunk-index-fepsbhr6.js → chunk-index-7mrkq793.js} +2 -2
- package/dist/chunk-index-7xcbpeqz.js +2 -0
- package/dist/chunk-index-88wh5eyj.js +2 -0
- package/dist/{chunk-index-6cg8xp1c.js → chunk-index-8pbwr3rs.js} +1 -1
- package/dist/{chunk-index-jc3r4qam.js → chunk-index-8wpvj7k5.js} +2 -2
- package/dist/chunk-index-97dcp1vh.js +1 -0
- package/dist/chunk-index-a7e6rhxy.js +2 -0
- package/dist/{chunk-index-2tr41qht.js → chunk-index-bxf8eq1s.js} +2 -2
- package/dist/chunk-index-c8gm1crh.js +120 -0
- package/dist/{chunk-index-2y8r4s4f.js → chunk-index-ce7hsda9.js} +2 -2
- package/dist/chunk-index-cfesmk1f.js +2 -0
- package/dist/{chunk-index-m81362sc.js → chunk-index-cmzvq3nf.js} +1 -1
- package/dist/{chunk-index-7qd604rq.js → chunk-index-d4mx2vvf.js} +1 -1
- package/dist/chunk-index-d74tpnkd.js +2 -0
- package/dist/chunk-index-d941ae7c.js +2 -0
- package/dist/{chunk-index-e35v554x.js → chunk-index-dr0jr7d3.js} +3 -3
- package/dist/chunk-index-en728705.js +2 -0
- package/dist/chunk-index-fb3kzyyp.js +2 -0
- package/dist/{chunk-index-3yy10a5h.js → chunk-index-ffnd0jcj.js} +2 -2
- package/dist/chunk-index-fn6305vn.js +3 -0
- package/dist/{chunk-index-zcfr8jdv.js → chunk-index-gdh7wsd5.js} +12 -12
- package/dist/{chunk-index-2hvn2kte.js → chunk-index-m76gdwnk.js} +2 -2
- package/dist/chunk-index-mr391t49.js +2 -0
- package/dist/chunk-index-n3khm9fq.js +2 -0
- package/dist/{chunk-index-sm7kfb07.js → chunk-index-nn4w6a04.js} +2 -2
- package/dist/{chunk-index-gwkhs8s3.js → chunk-index-nr275sa7.js} +2 -2
- package/dist/{chunk-index-x60n1me6.js → chunk-index-pgxgq61x.js} +2 -2
- package/dist/chunk-index-prq011b6.js +2 -0
- package/dist/{chunk-index-v9gbt5tz.js → chunk-index-q4k9vk04.js} +19 -3
- package/dist/chunk-index-rgdaec7y.js +2 -0
- package/dist/chunk-index-rjs1can6.js +2 -0
- package/dist/chunk-index-s4a8e4qm.js +2 -0
- package/dist/{chunk-index-s9p27f9b.js → chunk-index-sz381vda.js} +3 -3
- package/dist/{chunk-index-5hbq6g2e.css → chunk-index-t7csmbpy.css} +1 -1
- package/dist/{chunk-index-rgf4rvns.js → chunk-index-v9jqyr1f.js} +3 -3
- package/dist/{chunk-index-rsd8rftk.js → chunk-index-vpq2cj14.js} +2 -2
- package/dist/chunk-index-vsaggfns.js +2 -0
- package/dist/{chunk-index-p7jkr7gv.js → chunk-index-w17wgjby.js} +5 -5
- package/dist/{chunk-index-c7dbvkrq.js → chunk-index-wgrx2ybn.js} +2 -2
- package/dist/chunk-index-wsv46y9p.js +2 -0
- package/dist/{chunk-index-4t6vnwap.js → chunk-index-xbj816p0.js} +2 -2
- package/dist/{chunk-index-s53bxwkk.js → chunk-index-yr758n6t.js} +2 -2
- package/dist/{chunk-index-5t2jkecy.js → chunk-index-ysv36nbk.js} +2 -2
- package/dist/{chunk-index-zt19fdmc.js → chunk-index-ze9p4npm.js} +2 -2
- package/dist/chunk-index-zfdvyrjt.js +2 -0
- package/dist/chunk-lang-css-3h22se37.js +1 -0
- package/dist/{chunk-lang-dockerfile-n4bt8jbt.js → chunk-lang-dockerfile-y24eejnh.js} +1 -1
- package/dist/{chunk-lang-go-9k4y107e.js → chunk-lang-go-qzgv1161.js} +3 -3
- package/dist/chunk-lang-html-9rvrmmpg.js +1 -0
- package/dist/chunk-lang-javascript-4kv8s3tk.js +1 -0
- package/dist/{chunk-lang-json-hr12bzs1.js → chunk-lang-json-99v80v88.js} +1 -1
- package/dist/{chunk-lang-markdown-ttzcpgkg.js → chunk-lang-markdown-gn8he877.js} +4 -4
- package/dist/{chunk-lang-python-ntj8maem.js → chunk-lang-python-prsw4p4k.js} +2 -2
- package/dist/{chunk-lang-rust-gv8y859d.js → chunk-lang-rust-g6xqta16.js} +1 -1
- package/dist/{chunk-lang-shell-5zzs09b7.js → chunk-lang-shell-dmpnzc6w.js} +1 -1
- package/dist/{chunk-lang-sql-6vmze4pg.js → chunk-lang-sql-wz7ca27b.js} +2 -2
- package/dist/{chunk-lang-toml-tt0d4rw9.js → chunk-lang-toml-160prtq4.js} +1 -1
- package/dist/chunk-lang-typescript-mp0yw903.js +1 -0
- package/dist/{chunk-lang-xml-r2sbqnjv.js → chunk-lang-xml-9htkz2dd.js} +1 -1
- package/dist/{chunk-lang-yaml-ex31qbph.js → chunk-lang-yaml-08749wd5.js} +1 -1
- package/dist/chunk-layout-actions-tdx896xf.js +1 -0
- package/dist/chunk-menu-items-trhybv1g.js +1 -0
- package/dist/chunk-mode-toggle-4dtfphq4.js +1 -0
- package/dist/{chunk-note-row-40ypghn9.js → chunk-note-row-28e5x7n8.js} +33 -4
- package/dist/chunk-notes-5nkayhw8.js +1 -0
- package/dist/chunk-notice-bell-xz8a04am.js +1 -0
- package/dist/{chunk-notices-tefx93s6.js → chunk-notices-d0cwvk27.js} +1 -1
- package/dist/chunk-onboarding-zs4rtea9.js +1 -0
- package/dist/{chunk-overview-zrx3bpwf.js → chunk-overview-9sb3ywa8.js} +1 -1
- package/dist/chunk-palette-5sfhrv4x.js +1 -0
- package/dist/chunk-placement-items-e2neja55.js +1 -0
- package/dist/{chunk-profile-tyb48b79.js → chunk-profile-jffhtpqf.js} +1 -1
- package/dist/chunk-prompt-history-1jz2gk3v.js +1 -0
- package/dist/chunk-prompts-0t30ttd0.js +1 -0
- package/dist/chunk-room-confirms-6595921h.js +1 -0
- package/dist/{chunk-room-menu-ha1evv71.js → chunk-room-menu-hwkjyxwe.js} +1 -1
- package/dist/chunk-rooms-menu-button-k0eq1tcr.js +1 -0
- package/dist/{chunk-schedules-a5pewkhd.js → chunk-schedules-e12y8fw2.js} +1 -1
- package/dist/{chunk-seat-menu-eh3ymmks.js → chunk-seat-menu-n2bcw0mf.js} +1 -1
- package/dist/chunk-sheet-hosts-myvm2vhd.js +29 -0
- package/dist/chunk-shortcut-help-434sqnxc.js +26 -0
- package/dist/chunk-shortcuts-mjxph0g7.js +1 -0
- package/dist/chunk-skill-editor-98492yrt.js +8 -0
- package/dist/chunk-skills-mfv8ppfh.js +1 -0
- package/dist/chunk-start-over-line-m526193z.js +16 -0
- package/dist/{chunk-switcher-list-dtfh87zy.js → chunk-switcher-list-5ngjar24.js} +1 -1
- package/dist/{chunk-tab-dot-qd640mjr.js → chunk-tab-dot-tzjkahj5.js} +1 -1
- package/dist/chunk-table-xbtbex2z.js +1 -0
- package/dist/{chunk-table2d-5nrhfwft.js → chunk-table2d-61bvrykm.js} +1 -1
- package/dist/{chunk-table3d-5cb66d99.js → chunk-table3d-5a7vbgyj.js} +172 -172
- package/dist/chunk-thread-actions-bf6vf2mt.js +1 -0
- package/dist/{chunk-thread-cards-kgt22sbp.js → chunk-thread-cards-n0n603vf.js} +1 -1
- package/dist/chunk-thread-list-7bkdn51x.js +227 -0
- package/dist/{chunk-transcript-a0p3vwh6.js → chunk-transcript-8ztrkt6g.js} +1 -1
- package/dist/chunk-waiting-chip-y83c90v3.js +32 -0
- package/dist/chunk-waiting-s273kcgz.js +1 -0
- package/dist/chunk-where-jr791gzj.js +1 -0
- package/dist/chunk-workspace-dw71mshw.js +586 -0
- package/dist/chunk-zh-tw-vm5d0w6j.js +1 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +3 -3
- package/src/api-types.ts +51 -0
- package/src/app/app.ts +7 -0
- package/src/app/card-log.ts +10 -2
- package/src/app/protocol.ts +18 -0
- package/src/app/queued-delivery.ts +107 -0
- package/src/app/queued-frames.ts +115 -0
- package/src/app/room-log.ts +39 -7
- package/src/app/rooms.ts +30 -2
- package/src/app/routes.ts +123 -7
- package/src/app/skill-group-routes.ts +126 -0
- package/src/app/socket.ts +12 -2
- package/src/app/surface.ts +37 -6
- package/src/app/zip.ts +124 -0
- package/src/auth/sessions.ts +9 -1
- package/src/authz.ts +1 -0
- package/src/host/cards.ts +10 -3
- package/src/host/stored-json.ts +23 -0
- package/src/i18n/zh-tw.ts +45 -2
- package/src/notices/mount.ts +1 -0
- package/src/notices/store.ts +9 -1
- package/src/push/channel.ts +1 -0
- package/src/web-plugin.ts +5 -1
- package/dist/chunk-account-678dgbww.js +0 -1
- package/dist/chunk-agent-x0yvvdq2.js +0 -1
- package/dist/chunk-arranged-rooms-6rdkk8t1.js +0 -1
- package/dist/chunk-bulk-actions-zbwdaxtc.js +0 -1
- package/dist/chunk-character-creator-215psy17.js +0 -1
- package/dist/chunk-confirm-2pzrt272.js +0 -1
- package/dist/chunk-connectors-zha2d834.js +0 -37
- package/dist/chunk-conversations-fb071rez.js +0 -1
- package/dist/chunk-council-sheet-k5fycefd.js +0 -2
- package/dist/chunk-folder-list-qxhmczwv.js +0 -1
- package/dist/chunk-folds-b7asegfs.js +0 -1
- package/dist/chunk-group-scene-63yhh1mh.js +0 -1
- package/dist/chunk-group-table-zy4hmgk3.js +0 -1
- package/dist/chunk-host-icons-tyktazzz.js +0 -1
- package/dist/chunk-host-picture-ebhejn2k.js +0 -1
- package/dist/chunk-host-state-wajep0yr.js +0 -1
- package/dist/chunk-host-ui-pc9r24e3.js +0 -1
- package/dist/chunk-index-1af8vsyb.js +0 -2
- package/dist/chunk-index-254701sm.js +0 -3
- package/dist/chunk-index-31m1b2f5.js +0 -2
- package/dist/chunk-index-5vatmakn.js +0 -2
- package/dist/chunk-index-6yra9kdq.js +0 -2
- package/dist/chunk-index-9bdstf8t.js +0 -2
- package/dist/chunk-index-acj4xxgq.js +0 -100
- package/dist/chunk-index-aexy023z.js +0 -2
- package/dist/chunk-index-amxa2bn3.js +0 -2
- package/dist/chunk-index-bef0fc2m.js +0 -2
- package/dist/chunk-index-c6tphrjr.js +0 -1
- package/dist/chunk-index-cn49mawx.js +0 -50
- package/dist/chunk-index-gcmmwh8t.js +0 -2
- package/dist/chunk-index-h2vhv4kh.js +0 -2
- package/dist/chunk-index-h9ccz1r3.js +0 -2
- package/dist/chunk-index-n0ejy0wd.js +0 -2
- package/dist/chunk-index-nkdg1dfz.js +0 -7
- package/dist/chunk-index-qxynr1hf.js +0 -2
- package/dist/chunk-index-r7fdgzy1.js +0 -2
- package/dist/chunk-index-sdcpf5dp.js +0 -2
- package/dist/chunk-index-sw9r4aa4.js +0 -2
- package/dist/chunk-index-t5bmsqqd.js +0 -1
- package/dist/chunk-index-v8mma5np.js +0 -2
- package/dist/chunk-index-vsfsv8hd.js +0 -2
- package/dist/chunk-index-wxft0aqb.js +0 -2
- package/dist/chunk-index-zvvwxkev.js +0 -2
- package/dist/chunk-lang-css-7q1dtpgm.js +0 -1
- package/dist/chunk-lang-html-xgvba8rn.js +0 -1
- package/dist/chunk-lang-javascript-vgxmgd0a.js +0 -1
- package/dist/chunk-lang-typescript-2v652sh5.js +0 -1
- package/dist/chunk-layout-actions-bfqtg71d.js +0 -1
- package/dist/chunk-menu-items-rztgekfj.js +0 -1
- package/dist/chunk-mode-toggle-m5kgb7c7.js +0 -1
- package/dist/chunk-notes-a8nqffbf.js +0 -1
- package/dist/chunk-notice-bell-rdnfmtf2.js +0 -1
- package/dist/chunk-onboarding-wvh0s3vg.js +0 -1
- package/dist/chunk-palette-5xtdvy9a.js +0 -1
- package/dist/chunk-placement-items-rgyrktvy.js +0 -1
- package/dist/chunk-prompt-history-wv0vdsjv.js +0 -1
- package/dist/chunk-prompts-75hgb0t0.js +0 -1
- package/dist/chunk-room-confirms-bcvhpcry.js +0 -1
- package/dist/chunk-rooms-menu-button-ej3a2hyc.js +0 -1
- package/dist/chunk-sheet-hosts-d5zbwbbr.js +0 -29
- package/dist/chunk-shortcut-help-bh9ntk9s.js +0 -26
- package/dist/chunk-shortcuts-6em0atk9.js +0 -1
- package/dist/chunk-skill-editor-e43mwz01.js +0 -8
- package/dist/chunk-skills-vp107wrp.js +0 -1
- package/dist/chunk-start-over-line-xd1v2p0g.js +0 -16
- package/dist/chunk-table-svb2xhfd.js +0 -1
- package/dist/chunk-thread-actions-926eab0w.js +0 -1
- package/dist/chunk-thread-list-j2v1pqkz.js +0 -217
- package/dist/chunk-waiting-cdyz7r1g.js +0 -1
- package/dist/chunk-waiting-chip-58ttx9av.js +0 -28
- package/dist/chunk-where-9jp6g029.js +0 -1
- package/dist/chunk-workspace-d34njy4p.js +0 -578
- package/dist/chunk-zh-tw-y3jb20wc.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.29.1] - 2026-10-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- A message's files are one attachment block: a heading "Attachments (N)" in a bordered box unlike inline code, each file a full-width chip with its icon, name and size, on phones and desktops. Agent rooms, host rooms, notes and the round table's dialogue box all show it; a host room still says how many files were removed.
|
|
13
|
+
- "Download all" in the heading of a message with two or more files downloads them as one zip, from `GET api/files/zip?room=<room>&message=<id>`. The route answers the room and each file as the single file route does, so someone who may not read the room gets the same refusal. It packs only the files still kept, each under its own name (a repeated name becomes `name-2`, `name-3`), in `attachments-<YYYYMMDD-HHMM>.zip` (the message's UTC time), stored without compression, and refuses past 200 MB with a message to download the files one by one. The zip is named after when the message was sent on the host's clock (`attachments-20261031-2028.zip`); `OwnerAppOptions.timeZone` and `AppApiOptions.timeZone` carry the host's zone, UTC when absent.
|
|
14
|
+
- A bubble with files that is taller than about one screen opens with a paperclip line "N attachments", which scrolls to the block, smoothly unless the person asked for less motion.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- A swipe right opens the room drawer from anywhere in the left half of the screen, the left edge included, over the header and empty space as well as over the messages. It was the messages only, and not within 32 px of either edge. The right edge stays the system's Back, and the drawer closes by a swipe left from anywhere but there.
|
|
19
|
+
- Host rooms have the drawer too: the swipe, and a tap on the room's picture in the header. The waiting-cards strip of a host room moved to its own file.
|
|
20
|
+
- A message of yours sent while a turn runs shows where it stands under it, live and after a reload: "Added to this turn" when the running turn took it, "Queued" while it waits for its own turn (the line goes when the turn starts), and "Cancelled" for one you cancelled, which stays in the room dimmed and never runs. A queued message's long-press or right-click menu offers its author Edit (a sheet with the text; the turn runs on the new text and the message shows as edited) and Cancel. Once the turn has started a notice says it is too late. `RoomMessage.delivery` carries the state (`steered` and `cancelled` are stored, in a new `delivery` column; `queued` is read from the host's queue and so does not outlive a restart); client frames `edit_queued` and `cancel_queued` (`room`, `message` id, `text`) answer with the changed message as an `edited` frame or a `refused` frame (`not_queued` when the turn has started, `forbidden` for another person's message). With a core that has no `ConversationPort.cancelQueued` and `editQueued`, both answer `not_queued`.
|
|
21
|
+
- Pressing ↑ in an empty message box opens Edit on your latest message in the room that still waits for its turn (not while an input method composes; Alt+↑ / Alt+↓ still switch rooms), and the `?` shortcut list names it.
|
|
22
|
+
- The round table's camera turns to a member by the same rule as the spotlight: only when their words show. A member who only thinks or works no longer takes it when the previous speaker's words finish; the camera stays on whoever spoke last until the next member's words start.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- jsonb columns are stored as JSON values: a room message's `files`, `picture` and `intercom`, an ended card's `frame` and `answer`, a host card, a notice's `open` and a session's actor were stored as JSON strings. Migrations (`web-room-messages-json-values`, `web-room-cards-json-values`, `web-host-cards-json-values`, `web-notices-json-values`, `web-sessions-json-values`) convert the rows stored that way; reads still take either.
|
|
27
|
+
- Buttons with a quiet look of their own no longer turn into unreadable text on the accent fill when hovered or, on a phone, after a tap: the "N earlier tools" chip, the character creator's tiles, the files pane's folder twist and image toggle, the host room's cards toggle and seen faces, and a knight's temp knights header now say what hover does to them. A test fails for any new button that sets its own fill or text colour without a hover rule.
|
|
28
|
+
- The character creator's styles load with the creator instead of with the page, which leaves room in what the page loads first.
|
|
29
|
+
- The room drawer opens on a phone. A swipe right in a room never reached the page in Chrome: the room had the default `touch-action`, so Chrome took a sideways drag for itself a few pixels in and cancelled the touch before the swipe was 24 px long. The room, its message list, the composer and the drawer's own list now allow only up-down panning and pinch zoom, which leaves sideways drags to the page. A swipe that starts in a sideways-scrolling code block or table, a field, a card's option row, a sheet over the page, or a scene that takes every touch is still not the room's.
|
|
30
|
+
- Two edits of a waiting message from two sockets reach the room's log in the order the host took them, so the room shows the text the turn runs.
|
|
31
|
+
|
|
32
|
+
## [0.29.0] - 2026-10-31
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Skill groups in the app. The Skills page has a "Skill groups" section that lists each group with its skills and creates, edits (name, description, skills picked from the catalog) and deletes a group after a confirmation. The Edit knight sheet's Skills section has a switch per group, the same switch as each skill's: it shows on when the knight carries all of the group, otherwise what it lacks. Switching it on, after a confirmation, switches on the group's skills the knight lacks through the same path as the per-skill switch; switching it off switches off the group's skills the knight carries, built-in ones excepted. Either names what changed. A knight carries skills, not groups. The routes are under `api/skill-groups/` (list, `PUT` to create or replace with `from` to rename, `DELETE`, `POST …/apply` with `on: false` to switch a group off, answering `added` and `removed`) and answer owners only; `AppApiOptions.skills.groups` takes the registry's `groups`, `setGroup` and `deleteGroup`.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- A group's council has one view, `CouncilView`, that both the group room and the round table page show: the council chip and sheet, the Animation mode / Chat mode switch, Reset view in the scene, the waiting cards, the room menu, the mode and offline banners, and the scene or the chat with the room's composer (with its @AI/note switch where the room takes notes). The table page gains the waiting cards and the room menu; the room gains the council chip, seat menus in the scene, and a phone keyboard that closes after a send.
|
|
41
|
+
- The round table's dialogue box types a knight's words out whole before the scene moves on. A knight who starts thinking meanwhile shows its thinking on its seat while the camera and the box stay on the speaker; once the words stand, the box takes the next knight's words, or the camera turns to the knight still thinking and the box shows its progress under the last words. Lines said while one types wait and come in order, a tap still shows the whole line and moves on, and with reduced motion each line shows whole and the next follows at once. Both the 3D and the 2D table; the chat mode is unchanged.
|
|
42
|
+
- The mode is kept per council room (`roundtable-console:council-mode:<room>`) instead of a group room's "Table" toggle and the table page's per-device mode. The first read takes the room's old toggle (`rt-table-mode:<room>`, then removed), else the old device mode (`roundtable-console:table-mode`), else the scene.
|
|
43
|
+
- A temp knight made on Discord stays off the room list and the switcher until its room has a message or someone opens it from a link; `GET api/rooms/<key>` answers its view and lists it from then on. A card it waits on still shows. One made in the web app, or before its origin was kept, is listed as before.
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
47
|
+
- The group room's "Table" toggle button and the table page's own header, mode switch and chat wrapper.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- A swipe in a round table close-up steps the way the finger moves: a swipe to the left brings in the podium on the right. It went the other way.
|
|
52
|
+
- A room's chat stops following its newest end the moment the person scrolls up, by wheel, finger, key or scrollbar. Scrolling up just a little from the end used to count as still at the end, so rows settling or a reply streaming in pulled the view back down, and it shook.
|
|
53
|
+
|
|
8
54
|
## [0.28.0] - 2026-10-31
|
|
9
55
|
|
|
10
56
|
### Added
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Then only a person whose principal holds the owner role gets in.
|
|
|
30
30
|
|
|
31
31
|
## Requirements
|
|
32
32
|
|
|
33
|
-
- Bun 1.4.4 or later, and pi-roundtable `>=0.
|
|
33
|
+
- Bun 1.4.4 or later, and pi-roundtable `>=0.29.0 <0.30.0` as a peer dependency.
|
|
34
34
|
- The host must run on the machine that holds its data directory: the console reads `<dataDir>/sessions` from disk, and the app keeps its files and keys under `<dataDir>/web/`.
|
|
35
35
|
- An agent team: `agents` in the configuration, with or without `discord`. Without Discord the host also needs a surface that hosts rooms, and this plugin is one; the start fails otherwise, naming the fix.
|
|
36
36
|
- A way to authenticate the owners (see [Authentication](#authentication)): a password, Cloudflare Access or a verifier of your own in front of the listener, or `none` on a listener only the owner reaches.
|
|
@@ -157,7 +157,7 @@ The room list's menu (the button beside the search) and each folder's menu clear
|
|
|
157
157
|
|
|
158
158
|
#### Temp knights
|
|
159
159
|
|
|
160
|
-
A knight's temp knights (the helpers it makes with `helper_create`, see the core guide) hang under its row like threads: a small face, the name, and a state, a pulsing dot while it works, the card badge while it waits for you, and a dimmed **Ended** with the time once it has nothing running, queued or waiting. The group of them is unfolded while one is at work and folded when all have ended, as the person last chose on this device. They follow the knight's pin and folder, have no drag handle and no seat at the round table, are left out of groups, the council tick list and the switcher, and their push is held back by default because their answers reach the knight's room (a card still pushes). A temp knight's room works like any other room: you may write to it directly, and its header reads "Temp knight · Made by <knight>" with a link to the knight.
|
|
160
|
+
A knight's temp knights (the helpers it makes with `helper_create`, see the core guide) hang under its row like threads: a small face, the name, and a state, a pulsing dot while it works, the card badge while it waits for you, and a dimmed **Ended** with the time once it has nothing running, queued or waiting. The group of them is unfolded while one is at work and folded when all have ended, as the person last chose on this device. They follow the knight's pin and folder, have no drag handle and no seat at the round table, are left out of groups, the council tick list and the switcher, and their push is held back by default because their answers reach the knight's room (a card still pushes). A temp knight's room works like any other room: you may write to it directly, and its header reads "Temp knight · Made by <knight>" with a link to the knight. A temp knight lives on the surface that made it: one made here gets no Discord thread until someone messages it there, and one made on Discord stays off the room list, the switcher included, until its room has a message or you open it from a link (`GET api/rooms/<key>` answers its view and lists it from then on); a card it waits on still shows.
|
|
161
161
|
**Clear finished temp knights** is in the room list's menu, a folder's menu and a knight's menu, and is left out when nothing has ended. It archives at once, with no confirmation, and the notice's Undo restores exactly those. A temp knight's own menu has **Archive this temp knight**, with the same notice. When a knight clears its own with `helper_end`, its room shows a line naming them with an Undo. The archive page lists each temp knight right after its knight.
|
|
162
162
|
The routes are `POST api/rooms/archive-finished-helpers` (body `{ "rooms": [<keys>] }` of knights' rooms, at most 500; left out, every knight's; answers `{ "archived": [{ "room", "names" }] }`) and `POST api/rooms/restore-helpers` (body `{ "names": [<names>] }`, at most 500; answers `{ "restored": [<names>] }`; a name that is not a temp knight is refused with `400` and named, and one that is no longer archived is passed over). Both are the owners', like the rest of `api/rooms`. `RoomView.helper` is `{ parent, parentName, finished? }` on a temp knight's room.
|
|
163
163
|
|
|
@@ -251,14 +251,14 @@ The table is a **council**: one agent group (the same group as the group chat, w
|
|
|
251
251
|
This section describes the owners' table. A member or an admin is served the table only when the host names it for their tier, and theirs seats host rooms instead of agents: see [What each tier is served](#what-each-tier-is-served).
|
|
252
252
|
|
|
253
253
|
- **Cameras.** The overview stands behind your podium, raised. A drag or swipe turns it round the ring and snaps to a podium; the wheel or a pinch moves in on the podium across the ring and back out. A close-up of a speaker stands low inside the ring, tilted a little, with thin speed lines flashing for 400 ms on a new speaker. A turn follows the ring the shorter way and takes 450 to 650 ms. A swipe never starts within 32 px of the screen's edge. Under `prefers-reduced-motion` the camera cuts, with no speed lines, bob or flicker.
|
|
254
|
-
- **Talking in the scene.** The dialogue box at the bottom is the council's group room: a nameplate tab in the speaker's color with their picture, the line being said (Markdown, typed out while it streams, a tap shows it whole), the cards waiting for an answer, and the room's message box, with its unsent text kept per room. A message sent there starts a round under the group's rules. A tap on a podium (or on its name, a button over the plate) turns the camera to that member until the next line comes; **Reset view** brings it back to the whole ring; holding a seat (a long press, a right click or the Menu key) opens its menu with **Private chat** (the knight's own room) and **Remove from the council**. **Log** opens the room's whole message list as a sheet over the scene. **Collapse dialogue** folds the box to one bar (the speaker's face and name, the start of the newest line, and *Waiting for your answer* while a card waits) and **Expand dialogue**, or a tap anywhere on the bar, opens it again; the choice is kept per device (`roundtable-console:dialogue-folded`). On a phone (a narrow or touch screen) a message sent from the table closes the keyboard, so the answer has the screen; elsewhere the box keeps the focus for the next one. The header's **Animation mode** / **Chat mode** switch shows the council as the scene with its dialogue box, or as its group room's message list with the cards and the same message box (the unsent text carries over); the choice is kept per
|
|
254
|
+
- **Talking in the scene.** The dialogue box at the bottom is the council's group room: a nameplate tab in the speaker's color with their picture, the line being said (Markdown, typed out while it streams, a tap shows it whole), the cards waiting for an answer, and the room's message box, with its unsent text kept per room. A message sent there starts a round under the group's rules. A tap on a podium (or on its name, a button over the plate) turns the camera to that member until the next line comes; **Reset view** brings it back to the whole ring; holding a seat (a long press, a right click or the Menu key) opens its menu with **Private chat** (the knight's own room) and **Remove from the council**. **Log** opens the room's whole message list as a sheet over the scene. **Collapse dialogue** folds the box to one bar (the speaker's face and name, the start of the newest line, and *Waiting for your answer* while a card waits) and **Expand dialogue**, or a tap anywhere on the bar, opens it again; the choice is kept per device (`roundtable-console:dialogue-folded`). On a phone (a narrow or touch screen) a message sent from the table closes the keyboard, so the answer has the screen; elsewhere the box keeps the focus for the next one. The header's **Animation mode** / **Chat mode** switch shows the council as the scene with its dialogue box, or as its group room's message list with the cards and the same message box (the unsent text carries over); the choice is kept per council (`roundtable-console:council-mode:<room>`) and is the same in the group's room. The header also has the waiting cards and the room menu, as a room's does. The address is `#/table/<group>` (the group's name or the key of its room): without one the table shows the council this device chose last (kept in the browser's storage), or the first group, and an address that names no group goes back to `#/table`.
|
|
255
255
|
- **Names.** A seat's name is a button laid over its podium's nameplate, at least 12 px on the screen at any distance, in a font stack that carries Chinese (`system-ui`, PingFang TC, Noto Sans TC, Microsoft JhengHei); the podium's own plate is lettered the same way. The seat's full name and state are its accessible name. Where the names stand over each other (many seats on a phone) the agents are also listed as buttons under the scene.
|
|
256
256
|
- **Phones.** Held upright, the scene fills the top 55 % and the dialogue box the rest, and a folded box is only its bar, giving the scene the rest; on its side the scene is whole and the box lies over its bottom third.
|
|
257
257
|
- **Fallback.** Without WebGL (or after a lost context) the same ring is drawn as SVG in the same palette, with the same standees, plates and dialogue box, never a blank pane. `?nogl` in the page address (`/console/?nogl`, or `#/table?nogl`) draws it on a browser that has WebGL, to try that path; `nogl=0` and `nogl=false` leave the choice to the browser.
|
|
258
258
|
- **Performance.** The WebGL scene is a chunk of its own, fetched when the view first opens and cached by the service worker, never precached. It stops drawing while the page is hidden or the table is off screen, draws a few frames a second while nothing moves (so a torch flickers without keeping the GPU busy), and caps the pixel ratio at 2 (1.5 on phones).
|
|
259
259
|
- **The council.** The header's chip (**Council: <name> ▾**) opens a sheet, a bottom sheet on a phone and a dialog elsewhere. It lists the saved councils (the groups, with their members' faces; pressing one puts it on the table, with the same puff, glide and rise as a seat leaving or joining), and a tick list of every knight with a counter (**3 of 8 picked**): the ninth tick is refused and the sheet says *At most 8 knights*. Ticks keep the order they were made in, which is the group's tie order, and the host is chosen among the ticked. **Done** saves the ticks into the council on the table in place (`PATCH api/groups/<name>`); **Save as a new council** asks for a name and makes another group (`POST api/groups`), which opens its Discord channel as every group does. With no group yet the table says *No council yet* and the sheet starts from the knights that were not muted, the coordinator first, at most eight, to be named and saved. Who sits is no longer mute's say: mute only stops a room's push, stays in the knight's sheet and the room menu, and a muted knight sits when the council has it. Owners who used mute to take knights off the table see their first group (or the empty state) after this change.
|
|
260
260
|
- **Leaving and joining.** An agent who leaves goes in a manga-style puff: the standee squashes a little, flat outlined clouds pop up round it and hide it, then drift up and fade with a few sparkles (about 0.7 s). The other seats then glide to their new places with the camera. An agent who sits down is called up out of a small rune circle in the hall's teal that glows on the floor at its new place; the podium rises out of it with a slight bounce, under a column of light (about 0.85 s), once the others have opened the gap. The pictures are drawn on small canvases in the hall's style and given back when the show ends. Under `prefers-reduced-motion` the picture only dips and comes back (0.16 s each way), with no smoke or rise; the 2D fallback fades the seat in over a ring and out inside a puff with CSS.
|
|
261
|
-
- **Group rooms.** The same scene is a group room's: its header
|
|
261
|
+
- **Group rooms.** The same scene is a group room's: a group room shows the same council view as the table page: its header keeps the room's way back, picture and name, and adds the council chip (picking another council in its sheet opens that group's room), the **Animation mode** / **Chat mode** switch, **Reset view** in the scene, the waiting cards and the room menu. The mode is kept per council, so one chosen in the room shows on the table page and back. A council read for the first time takes the room's old "Table" toggle (`rt-table-mode:<room>`, removed once moved), else the table page's old device mode (`roundtable-console:table-mode`), else the scene. In the scene the whole conversation runs in the dialogue box, the camera turns to whoever replies, and it rests on the whole ring while you speak; a member that starts thinking shows its badge wherever the camera is, and a speaker change never takes the focus from your message box. Under the Camelot look a compact strip of the members stands under the header in chat mode, with the speaker lit. On a phone a message sent in either mode closes the keyboard.
|
|
262
262
|
- **Turn-taking.** A group round is the members' answers to one message, one after another. The server tells the page where it stands in a `round` frame (`{ type: "round", room, round }`, and `RoomView.round` for a page that opens mid-round): `id` (the group message that opened it), `state`, `current`, `next` (the queue, a handoff appended), `spoken`, `replies` and `limit` (8), `ended` (`done`, `limit` or `stopped`) and `queued` (your messages waiting for the next round). An **order strip** on top of the dialogue box shows the faces in that order: those who spoke dimmed with a check, the one speaking larger, those who are next numbered, and a chip, *Reply 3 of 8*, that reads *Limit reached*, *Round stopped* or *n of 8 replies* once it ended; the strip of the last round stays until the next starts. While it runs the seats wear the same signs before their names (a check, or ① to ⑧; a round that ended leaves them bare), the member whose turn runs thinks (its seat bobs with the thinking badge, no light) until its words show, and its seat is lit only while its words are on screen: streaming in, or typed out in the dialogue box until the last of them shows (on a group room's page, while they stream); the camera turns to it as soon as its turn starts, not when its line arrives; a camera you turned by hand stays until the next line or the next speaker. While a message of yours waits, the strip says so. The box's stop button reads **Stop the round**: it stops the member speaking and the ones waiting do not speak, on the web and in Discord alike.
|
|
263
263
|
|
|
264
264
|
### The files pane
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{p,T,x,e,v}from"./chunk-index-24cggw6k.js";var a={profile:x(()=>(p("jffhtpqf"),import("./chunk-profile-jffhtpqf.js")).then((o)=>({default:o.ProfilePage}))),notices:x(()=>(p("d0cwvk27"),import("./chunk-notices-d0cwvk27.js")).then((o)=>({default:o.NoticesPage}))),onboarding:x(()=>(p("zs4rtea9"),import("./chunk-onboarding-zs4rtea9.js")).then((o)=>({default:o.OnboardingPage})))};function n({pane:o}){let t=a[o];return t?e(T,{fallback:e(v,{}),children:e(t,{})}):null}export{n as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o
|
|
1
|
+
import{o,Te,a,r,g,e,n,s,wt,ds}from"./chunk-index-24cggw6k.js";import{R}from"./chunk-index-cmzvq3nf.js";var L=()=>typeof matchMedia==="function"&&matchMedia("(max-width: 719px)").matches,k={rtMenu:!0},x=()=>Boolean(window.history.state?.rtMenu),w=8,E=(t)=>t?.isConnected?t:null;function M(t){return t.items.map((i)=>n("button",{type:"button",role:i.checked===void 0?"menuitem":"menuitemcheckbox",...i.checked===void 0?{}:{"aria-checked":i.checked},className:"menu-item menu-link","data-tone":i.tone,onClick:()=>t.onChoose(i),children:[i.icon?e("span",{className:"menu-icon","data-tone":i.tone,children:e(s,{name:i.icon,size:18})}):null,e("span",{className:"menu-text",children:e("strong",{children:i.label})}),i.checked?e(s,{name:"check",size:18,className:"menu-check"}):null]},i.id))}function H({menu:t}){let i=a(void 0);if(t)i.current=t;let l=i.current,m=a(!1),h=a(void 0),p=t?.serial;return o(()=>{if(p===void 0)return;return window.history.pushState(k,"",window.location.href),m.current=!0,()=>{if(m.current)m.current=!1,window.history.back()}},[p]),o(()=>{let f=()=>{if(!m.current||x())return;m.current=!1,wt();let y=h.current;h.current=void 0,y?.()};return window.addEventListener("popstate",f),()=>window.removeEventListener("popstate",f)},[]),e(R,{open:t!==void 0,title:l?.title??"",onClose:wt,restoreFocus:()=>E(l?.source??null),children:l?e("div",{className:"menu action-items",role:"menu","aria-label":l.title,children:e(M,{items:l.items,onChoose:(f)=>{if(!m.current){wt(),f.run();return}h.current=f.run,window.history.back()}})}):null})}function T({menu:t}){let i=a(null),[l,m]=r(t.at),{source:h}=t,p=(u)=>{E(h)?.focus(),wt(),u?.()},f=a(p);f.current=p,Te(()=>{let u=i.current;if(!u)return;let d={left:u.style.left,top:u.style.top};u.style.left="0px",u.style.top="0px";let c={width:u.offsetWidth,height:u.offsetHeight};u.style.left=d.left,u.style.top=d.top,m({x:Math.max(w,Math.min(t.at.x,window.innerWidth-c.width-w)),y:Math.max(w,Math.min(t.at.y,window.innerHeight-c.height-w))}),u.querySelector('[role^="menuitem"]')?.focus()},[t.serial]),o(()=>{let u=(c)=>{if(c.target instanceof Node&&i.current?.contains(c.target))return;wt()},d=()=>wt();return document.addEventListener("pointerdown",u,!0),document.addEventListener("wheel",u,{capture:!0,passive:!0}),document.addEventListener("touchmove",u,{capture:!0,passive:!0}),window.addEventListener("resize",d),()=>{document.removeEventListener("pointerdown",u,!0),document.removeEventListener("wheel",u,!0),document.removeEventListener("touchmove",u,!0),window.removeEventListener("resize",d)}},[]);let y=(u)=>{let d=[...i.current?.querySelectorAll('[role^="menuitem"]')??[]],c=d.indexOf(document.activeElement),v=(b)=>{u.preventDefault(),d[(b+d.length)%d.length]?.focus()};switch(u.key){case"Escape":u.preventDefault(),u.stopPropagation(),f.current();return;case"ArrowDown":return v(c+1);case"ArrowUp":return v(c<0?-1:c-1);case"Tab":return v(u.shiftKey?c<0?-1:c-1:c+1);case"Home":return v(0);case"End":return v(-1)}};return e("div",{ref:i,className:"action-popover",role:"menu","aria-label":t.title,tabIndex:-1,style:{left:`${l.x}px`,top:`${l.y}px`},onKeyDown:y,children:e(M,{items:t.items,onChoose:(u)=>p(u.run)})})}function I(){let t=ds(),i=a(void 0);if(t&&i.current?.serial!==t.serial)i.current={serial:t.serial,sheet:L()};let l=Boolean(t&&i.current?.sheet);return n(g,{children:[e(H,{menu:l?t:void 0}),t&&!l?e(T,{menu:t},t.serial):null]})}export{I as ActionMenuHost};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{o,e,X,d,v,Ye}from"./chunk-index-24cggw6k.js";function g({name:r}){let m=d((i)=>i.listed),t=d((i)=>Object.values(i.rooms).find((n)=>n.view?.kind==="agent"&&n.view.name===r)?.view?.key,r);return o(()=>{if(t)X("room",t,{replace:!0}),Ye(r);else if(m)X("rooms",void 0,{replace:!0})},[t,m,r]),e("div",{className:"page",children:e(v,{})})}export{g as AgentPage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{p,T,x,ee,C,a,t,g,e,n,s,Ae,d,k,_,ge,jt,xn,te,yr,Qi,Jr,Xt,Mi,eo}from"./chunk-index-24cggw6k.js";import{ze}from"./chunk-index-3n38xe7m.js";import{yi}from"./chunk-index-d74tpnkd.js";import"./chunk-index-5j1v2q7w.js";import"./chunk-index-nr275sa7.js";import{ci}from"./chunk-index-fb3kzyyp.js";import{mi,gi}from"./chunk-index-sz381vda.js";import{Zt}from"./chunk-index-bxf8eq1s.js";import"./chunk-index-5w6tb7p0.js";import"./chunk-index-yr758n6t.js";import{st}from"./chunk-index-6rdywten.js";mi();var M=x(()=>(p("bs4eh3g9"),import("./chunk-folder-list-bs4eh3g9.js")).then((o)=>({default:o.FolderList}))),O=ee(function(l){let{view:r}=l,f=Zt(()=>jt(r.key),r.home?void 0:gi(r.key));return e(Jr,{...l,press:f,item:{...r.home?{}:{"data-drop-item":"","data-key":r.key},...l.arranging?{"data-arranging":""}:{}},after:l.arranging?e("button",{type:"button",className:"icon-button row-more","aria-haspopup":"menu","aria-label":t("Room menu"),onClick:(c)=>{let u=c.currentTarget;jt(r.key).then((i)=>{if(!i)return;let y=u.getBoundingClientRect();ge({...i,at:{x:y.left,y:y.bottom},via:"key",source:u})},()=>{return})},children:e(s,{name:"more",size:18})}):null})});function L(o){let{now:l}=te(),{timeZone:r}=_(),f=Xt(o.rooms);return e("ul",{className:"room-list",children:f.map(({key:c,...u})=>{let i=o.threads[c];return e(O,{...u,active:c===o.active,now:l,timeZone:r,arranging:o.arranging,...i?{threads:e(Mi,{open:i.open,closed:i.closed,active:o.active,parent:c})}:{}},c)})})}function v(o){let r=Xt(o.rooms).length===0;if(r&&o.zone?.kind!=="pinned")return null;return n("section",{className:"stack",...r?{"data-empty":""}:{},...o.zone?{"data-drop-zone":o.zone.kind,"data-zone-id":o.zone.id}:{},children:[e("h2",{className:"section-title",children:o.title}),r?null:e(L,{rooms:o.rooms,threads:o.threads,active:o.active,arranging:o.arranging})]})}function re(o){let l=Boolean(_().kinds?.length),r=d((m)=>JSON.stringify({listed:m.listed,...st(m,(F)=>Ae(m,F))})),f=yi(),c=a(r);if(!f)c.current=r;let u=c.current,{listed:i,...y}=C(()=>JSON.parse(u),[u]),{home:w,pinned:z,folders:N,restAgents:P,restGroups:D,rest:S,threads:A,orphans:h}=y,R=yr(),K=w.length+z.length+S.length===0&&N.every((m)=>m.rooms.length===0)&&h.open.length+h.closed.length===0,b={threads:A,active:o.active,arranging:R};return n("div",{className:"page","data-column":o.active?"":void 0,"data-drop-root":"",children:[n("header",{className:"page-head",children:[e("h1",{children:t("Rooms")}),n("div",{className:"actions",children:[n("button",{type:"button",className:"header-button search-button",onClick:o.onSearch,children:[e(s,{name:"search",size:16}),e("span",{children:t("Find a room")}),e("kbd",{children:"⌘K"})]}),i?e("button",{type:"button",className:"header-button icon-button","aria-haspopup":"menu","aria-label":t("Folders and order"),onClick:(m)=>ci(m.currentTarget),children:e(s,{name:"more",size:18})}):null,e("button",{type:"button",className:"header-button icon-button","aria-label":t("Add"),"aria-haspopup":"menu",onClick:(m)=>xn(m.currentTarget),children:e(s,{name:"plus"})})]})]}),R?n("div",{className:"arrange-bar",children:[n("button",{type:"button",className:"secondary",onClick:()=>ze({kind:"new"}),children:[e(s,{name:"plus",size:16}),e("span",{children:t("Add a folder")})]}),e("button",{type:"button",onClick:()=>Qi(!1),children:t("Done")})]}):null,i&&K&&!l?e(k,{children:t("No rooms yet. Rooms appear when the team has a knight.")}):null,!i?n("div",{className:"stack","aria-busy":"true",children:[e("div",{className:"skeleton"}),e("div",{className:"skeleton"}),e("div",{className:"skeleton"})]}):null,e(v,{title:t("Home"),rooms:w,...b}),e(v,{title:t("Pinned"),rooms:z,zone:{kind:"pinned",id:"pinned"},...b}),N.length>0?e("div",{className:"stack folder-list","data-folder-list":"",children:e(T,{fallback:e("div",{className:"skeleton","aria-busy":"true"}),children:e(M,{folders:N,active:o.active,listed:i,rooms:(m)=>e(L,{rooms:m,threads:A,active:o.active,arranging:R})})})}):null,N.length>0?e(v,{title:t("Ungrouped"),rooms:S,zone:{kind:"rest",id:"rest"},...b}):n(g,{children:[e(v,{title:t("Knights"),rooms:P,zone:{kind:"rest",id:"rest-agents"},...b}),e(v,{title:t("Groups"),rooms:D,zone:{kind:"rest",id:"rest-groups"},...b})]}),h.open.length+h.closed.length>0?n("section",{className:"stack",children:[e("h2",{className:"section-title",children:t("Dispatch threads")}),e(Mi,{open:h.open,closed:h.closed,active:o.active})]}):null,e(eo,{active:o.active})]})}export{re as ArrangedRoomsPage};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import{Te,a,r,t,G,e,n,s,Se,Wt,ls,Pe,$t,Os}from"./chunk-index-24cggw6k.js";import{Y}from"./chunk-index-d941ae7c.js";var u=`
|
|
2
|
+
.attachments {
|
|
3
|
+
margin-top: var(--space-3);
|
|
4
|
+
padding: var(--space-2);
|
|
5
|
+
border: 1px solid var(--border-strong);
|
|
6
|
+
border-radius: var(--radius);
|
|
7
|
+
background: var(--bg);
|
|
8
|
+
color: var(--text);
|
|
9
|
+
min-width: 0;
|
|
10
|
+
scroll-margin-top: var(--space-6);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.bubble > .attachments:first-child {
|
|
14
|
+
margin-top: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.attachments-head {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: var(--space-2);
|
|
21
|
+
min-height: 2rem;
|
|
22
|
+
padding: 0 var(--space-1);
|
|
23
|
+
font-size: var(--text-sm);
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.attachments-head > span {
|
|
28
|
+
flex: 1;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.attachments .link-button {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--space-1);
|
|
36
|
+
border: 1px solid transparent;
|
|
37
|
+
border-radius: var(--radius);
|
|
38
|
+
font-size: var(--text-sm);
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.attachments .files {
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
flex-wrap: nowrap;
|
|
47
|
+
margin-top: var(--space-1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.attachments .file-chip {
|
|
51
|
+
display: flex;
|
|
52
|
+
width: 100%;
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
min-height: 2.75rem;
|
|
55
|
+
border: 1px solid var(--border);
|
|
56
|
+
border-radius: var(--radius);
|
|
57
|
+
background: var(--surface);
|
|
58
|
+
font-family: inherit;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.attachments .file-chip > svg {
|
|
62
|
+
flex: none;
|
|
63
|
+
color: var(--accent);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.attachments .file-name {
|
|
67
|
+
flex: 1;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.attachments .file-size {
|
|
75
|
+
flex: none;
|
|
76
|
+
color: var(--muted);
|
|
77
|
+
font-size: var(--text-xs);
|
|
78
|
+
font-variant-numeric: tabular-nums;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.message[data-mine] .attachments .file-chip {
|
|
82
|
+
background: var(--surface);
|
|
83
|
+
color: var(--text);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.attachments-jump {
|
|
87
|
+
display: inline-flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: var(--space-1);
|
|
90
|
+
margin: 0 0 var(--space-2);
|
|
91
|
+
font-size: var(--text-sm);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.attachments-jump[hidden] {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.message[data-mine] .attachments-jump {
|
|
99
|
+
color: inherit;
|
|
100
|
+
text-decoration: underline;
|
|
101
|
+
}
|
|
102
|
+
`;function h({file:o}){let i=$t(()=>Os(o));return o.type.startsWith("image/")?e("li",{children:e("a",{...i,href:Wt(o),target:"_blank",rel:"noreferrer noopener",children:e("img",{src:Wt(o),alt:o.name,loading:"lazy",decoding:"async",className:"file-image"})})}):e("li",{children:n("a",{...i,className:"file-chip",href:Wt(o),download:o.name,title:o.name,children:[e(s,{name:"file",size:16}),e("span",{className:"file-name",children:o.name}),e("span",{className:"file-size",children:Pe(o.size)})]})})}function f(o){Y("attachments",u);let{files:i,message:m}=o;return n("section",{className:"attachments","aria-label":t("Attachments ({count})",{count:i.length}),children:[n("div",{className:"attachments-head",children:[e(s,{name:"attach",size:16}),e("span",{children:t("Attachments ({count})",{count:i.length})}),m&&i.length>=2?n("a",{className:"link-button",href:ls(m),download:!0,children:[e(s,{name:"download",size:16}),t("Download all")]}):null]}),e("ul",{className:"files",children:i.map((c)=>e(h,{file:c},c.id))})]})}var g=0.9;function A({count:o}){Y("attachments",u);let i=a(null),[m,c]=r(!1);return Te(()=>{let l=i.current?.closest(".bubble");if(!l)return;let d=()=>c(l.getBoundingClientRect().height>window.innerHeight*g);d(),window.addEventListener("resize",d);let p=typeof ResizeObserver==="function"?new ResizeObserver(d):void 0;return p?.observe(l),()=>{window.removeEventListener("resize",d),p?.disconnect()}},[]),n("button",{ref:i,type:"button",className:"link-button attachments-jump",hidden:!m,onClick:(l)=>{l.stopPropagation(),i.current?.closest(".bubble")?.querySelector(".attachments")?.scrollIntoView({behavior:Se()?"auto":"smooth",block:"start"})},children:[e(s,{name:"attach",size:14}),G(o,"{count} attachment","{count} attachments")]})}export{A as AttachmentsJump,f as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t,G,O,l,f}from"./chunk-index-24cggw6k.js";import{sn}from"./chunk-index-nr275sa7.js";import"./chunk-index-5j1v2q7w.js";import{Qn,so}from"./chunk-index-ysv36nbk.js";import{So}from"./chunk-index-5w6tb7p0.js";import{re}from"./chunk-index-wgrx2ybn.js";import"./chunk-index-yr758n6t.js";var s=500,c=(n)=>{let r=[];for(let e=0;e<n.length;e+=s)r.push(n.slice(e,e+s));return r},i=()=>O.dispatch({type:"changed"});async function w(n){let r=So(n),e=Object.keys(r);if(e.length===0)return;let a=[];try{for(let o of c(e))a.push(...(await Qn.readAll(o)).moved)}catch(o){f(l(o),"danger"),i();return}sn(Object.fromEntries(e.map((o)=>[o,0]))),f(G(e.length,"Marked {count} room as read","Marked {count} rooms as read"),"info",a.length?{label:t("Undo"),run:()=>void m(a,r)}:void 0)}async function m(n,r){try{for(let e of c(n)){let{restored:a}=await Qn.unreadAll(e);sn(Object.fromEntries(a.map((o)=>[o,r[o]??0])))}}catch(e){f(l(e),"danger")}i()}var P=(n)=>so(n);async function d(n){try{await Qn.restoreHelpers(n)}catch(r){f(l(r),"danger")}i()}async function R(n){let r;try{r=(await Qn.archiveFinishedHelpers(n)).archived.flatMap((a)=>a.names)}catch(e){f(l(e),"danger"),i();return}if(i(),r.length===0)return;f(G(r.length,"Archived {count} temp knight","Archived {count} temp knights"),"info",{label:t("Undo"),run:()=>void d(r)})}async function T(n,r){try{await re.archiveAgent(n)}catch(e){f(l(e),"danger"),i();return}i(),f(t("Archived {name}",{name:r}),"info",{label:t("Undo"),run:()=>void d([n])})}export{T as archiveHelper,P as clearAllFinished,R as clearFinishedHelpers,w as markAllRead,d as putBackHelpers};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import{je,Jt,W,Ri,Pn}from"./chunk-index-7mrkq793.js";import{ee,Fr,o,L,C,a,r,t,u,g,e,n,xi,fe,y,l,f,Qe,Sn,he,Je}from"./chunk-index-24cggw6k.js";import{D}from"./chunk-index-6btdp13p.js";import{R}from"./chunk-index-cmzvq3nf.js";import{Y}from"./chunk-index-d941ae7c.js";var ue={"hat.none":"No hat","hat.goggcap":"Leather cap","hat.tallcap":"Burgundian felt cap","hat.softcap":"Soft cap","hat.hardhat":"Iron skullcap","hat.fcap":"Hunter's cap","hat.winghelm":"Winged helm","hat.helm":"Helm","hat.mortar":"Biretta","hat.beret":"Chaperon","hat.goggles":"Pointed cap","hat.tricorn":"Pilgrim hat","hat.sailor":"Straw hat","hat.kettle":"Kettle hat","hat.cavalier":"Feathered hat","hat.toque":"Linen cook's cap","hair.none":"No hair","beard.none":"No beard","accessory.none":"No accessory","prop.beads":"Prayer beads","prop.marotte":"Marotte","prop.map":"Chart","prop.flask":"Alchemist's phial","prop.telescope":"Astrolabe","prop.wrench":"Dividers","outfit.surplice":"Surplice","outfit.overalls":"Work jerkin","outfit.armor":"Mail hauberk","outfit.apron":"Smith's apron","outfit.chef":"Cook's tunic","outfit.coat":"Doublet","outfit.vest":"Pourpoint","outfit.sailor":"Mariner's tunic","hair.bob":"Bob","hair.bun":"Bun","hair.ponytail":"Ponytail"},X=(i)=>i.charAt(0).toUpperCase()+i.slice(1);function x(i,c){let h=`character.${i}.${c}`,p=t(h);if(p!==h)return p;if(i==="class")return W.classes[c]?.label??X(c);return ue[`${i}.${c}`]??X(c)}function Z(i){let c=`character.jobs.${i}`,h=t(c);return h!==c?h:W.classes[i]?.jobs??""}var ae=`
|
|
2
|
+
.creator {
|
|
3
|
+
display: grid;
|
|
4
|
+
gap: var(--space-5);
|
|
5
|
+
align-items: start;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@media (min-width: 720px) {
|
|
9
|
+
.creator {
|
|
10
|
+
grid-template-columns: 13rem minmax(0, 1fr);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.creator-preview {
|
|
14
|
+
position: sticky;
|
|
15
|
+
top: var(--space-4);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.creator-preview {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--space-3);
|
|
24
|
+
padding: var(--space-4);
|
|
25
|
+
border: 1px solid var(--border);
|
|
26
|
+
border-radius: var(--radius-lg);
|
|
27
|
+
background: color-mix(
|
|
28
|
+
in srgb,
|
|
29
|
+
var(--agent-accent, var(--accent)) 8%,
|
|
30
|
+
var(--surface)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.creator-body {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: var(--space-4);
|
|
38
|
+
min-width: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.creator-field {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: var(--space-2);
|
|
45
|
+
min-width: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.creator-field h3 {
|
|
49
|
+
font-size: var(--text-sm);
|
|
50
|
+
color: var(--muted);
|
|
51
|
+
font-weight: var(--weight-medium);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* In the phone's sheet the sheet's own title says what the group is. */
|
|
55
|
+
dialog.sheet dialog.sheet .creator-field > h3 {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.creator-grid {
|
|
60
|
+
display: grid;
|
|
61
|
+
grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
|
|
62
|
+
gap: var(--space-2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.creator-strip {
|
|
66
|
+
display: flex;
|
|
67
|
+
gap: var(--space-2);
|
|
68
|
+
/* Room for the chosen tile's ring, which the scrolling strip would otherwise clip. */
|
|
69
|
+
padding: 3px 3px var(--space-1);
|
|
70
|
+
overflow-x: auto;
|
|
71
|
+
scroll-snap-type: x proximity;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* The row fades toward the side where more of it lies, so a cut-off tile reads as "scroll" and not as the end. */
|
|
75
|
+
.creator-strip[data-more="end"] {
|
|
76
|
+
-webkit-mask-image: linear-gradient(
|
|
77
|
+
to right,
|
|
78
|
+
#000 calc(100% - 2.5rem),
|
|
79
|
+
transparent
|
|
80
|
+
);
|
|
81
|
+
mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
|
|
82
|
+
}
|
|
83
|
+
.creator-strip[data-more="start"] {
|
|
84
|
+
-webkit-mask-image: linear-gradient(
|
|
85
|
+
to left,
|
|
86
|
+
#000 calc(100% - 2.5rem),
|
|
87
|
+
transparent
|
|
88
|
+
);
|
|
89
|
+
mask-image: linear-gradient(to left, #000 calc(100% - 2.5rem), transparent);
|
|
90
|
+
}
|
|
91
|
+
.creator-strip[data-more="both"] {
|
|
92
|
+
-webkit-mask-image: linear-gradient(
|
|
93
|
+
to right,
|
|
94
|
+
transparent,
|
|
95
|
+
#000 2.5rem,
|
|
96
|
+
#000 calc(100% - 2.5rem),
|
|
97
|
+
transparent
|
|
98
|
+
);
|
|
99
|
+
mask-image: linear-gradient(
|
|
100
|
+
to right,
|
|
101
|
+
transparent,
|
|
102
|
+
#000 2.5rem,
|
|
103
|
+
#000 calc(100% - 2.5rem),
|
|
104
|
+
transparent
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.creator-tile {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex: none;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 2px;
|
|
114
|
+
min-width: 4.25rem;
|
|
115
|
+
padding: var(--space-2) var(--space-1);
|
|
116
|
+
border: 1px solid var(--border);
|
|
117
|
+
border-radius: var(--radius);
|
|
118
|
+
background: var(--surface);
|
|
119
|
+
color: var(--text);
|
|
120
|
+
font-size: var(--text-xs);
|
|
121
|
+
scroll-snap-align: start;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.creator-grid .creator-tile {
|
|
125
|
+
min-width: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.creator-tile span {
|
|
129
|
+
max-width: 100%;
|
|
130
|
+
overflow: hidden;
|
|
131
|
+
text-overflow: ellipsis;
|
|
132
|
+
white-space: nowrap;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.creator-tile:hover:not(:disabled) {
|
|
136
|
+
border-color: var(--control-border);
|
|
137
|
+
background: var(--surface-2);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.creator-tile[aria-pressed="true"],
|
|
141
|
+
.creator-tile[aria-pressed="true"]:hover:not(:disabled),
|
|
142
|
+
.creator-swatch[aria-pressed="true"],
|
|
143
|
+
.creator-swatch[aria-pressed="true"]:hover:not(:disabled) {
|
|
144
|
+
border-color: var(--agent-accent, var(--accent));
|
|
145
|
+
background: color-mix(
|
|
146
|
+
in srgb,
|
|
147
|
+
var(--agent-accent, var(--accent)) 14%,
|
|
148
|
+
var(--surface)
|
|
149
|
+
);
|
|
150
|
+
box-shadow: 0 0 0 2px var(--agent-accent, var(--accent));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.creator-swatches {
|
|
154
|
+
display: flex;
|
|
155
|
+
flex-wrap: wrap;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: var(--space-2);
|
|
158
|
+
margin: 0;
|
|
159
|
+
padding: 0;
|
|
160
|
+
border: 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.creator-swatch {
|
|
164
|
+
width: 2.25rem;
|
|
165
|
+
height: 2.25rem;
|
|
166
|
+
min-height: 0;
|
|
167
|
+
padding: 0;
|
|
168
|
+
border: 2px solid var(--surface);
|
|
169
|
+
border-radius: var(--radius-full);
|
|
170
|
+
box-shadow: 0 0 0 1px var(--control-border);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.creator-custom {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-wrap: wrap;
|
|
176
|
+
gap: var(--space-4);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.creator-custom label {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
align-items: flex-start;
|
|
183
|
+
gap: var(--space-1);
|
|
184
|
+
font-size: var(--text-sm);
|
|
185
|
+
color: var(--muted);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.creator-custom input[type="color"],
|
|
189
|
+
.creator-swatches input[type="color"] {
|
|
190
|
+
width: 2.25rem;
|
|
191
|
+
height: 2.25rem;
|
|
192
|
+
padding: 0;
|
|
193
|
+
border: 1px solid var(--control-border);
|
|
194
|
+
border-radius: var(--radius-sm);
|
|
195
|
+
background: transparent;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.field-error {
|
|
199
|
+
margin: 0;
|
|
200
|
+
color: var(--danger);
|
|
201
|
+
font-size: var(--text-sm);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.creator-sections {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-direction: column;
|
|
207
|
+
gap: var(--space-2);
|
|
208
|
+
margin: 0;
|
|
209
|
+
padding: 0;
|
|
210
|
+
list-style: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.creator-section {
|
|
214
|
+
display: flex;
|
|
215
|
+
width: 100%;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: space-between;
|
|
218
|
+
gap: var(--space-3);
|
|
219
|
+
text-align: left;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.creator-actions {
|
|
223
|
+
flex-wrap: wrap;
|
|
224
|
+
}
|
|
225
|
+
`;var K=[{field:"hat",title:u("Hat"),ids:W.hats},{field:"outfit",title:u("Outfit"),ids:W.outfits},{field:"prop",title:u("Prop"),ids:W.props},{field:"hair",title:u("Hair"),ids:W.hair},{field:"beard",title:u("Beard"),ids:["none",...W.beards]},{field:"accessory",title:u("Accessory"),ids:["none",...W.accessories]}],pe=new Set(["class","colors","palette","build","skin","hairColor",...K.map((i)=>i.field)]),oe=Object.keys(W.palettes),te={idle:u("Idle"),thinking:u("Thinking"),working:u("Working"),waiting:u("Waiting for you"),done:u("Done"),error:u("Error"),sleepy:u("Sleepy")},ge={slim:u("Slim"),regular:u("Regular"),stout:u("Stout")};function M(i){return{class:i.class,outfit:i.outfit,hat:i.hat,prop:i.prop,hair:i.hair,beard:i.beard,accessory:i.accessory,build:i.build,skin:i.skin,hairColor:i.hairColor,colors:{...i.colors}}}var re=(i)=>JSON.stringify(M(i)),k=(i)=>i[Math.floor(Math.random()*i.length)];function be(){return{class:k(je),outfit:k(W.outfits),hat:k(W.hats),prop:k(W.props),hair:k(W.hair),beard:k(["none",...W.beards]),accessory:k(["none",...W.accessories]),build:k(["slim","regular","stout"]),skin:k([0,1,2,3,4,5]),hairColor:k(W.hairColors),palette:k(oe)}}var ne=ee(function(c){return n("button",{type:"button",className:"creator-tile","aria-pressed":c.pressed,title:c.hint?`${c.label} — ${c.hint}`:c.label,onClick:c.onPick,...c.data,children:[e(he,{spec:c.spec,size:c.size,live:!1}),e("span",{children:c.label})]})});function ve(i,c,h){let p=JSON.stringify({...i,[c]:0});return C(()=>h.map((d)=>({...i,[c]:d})),[p,c,h])}function ye(){let i=a(null),[c,h]=r();return o(()=>{let p=i.current;if(!p)return;let d=()=>{let S=p.scrollLeft>1,P=p.scrollLeft+p.clientWidth<p.scrollWidth-1;h(S&&P?"both":P?"end":S?"start":void 0)};d(),p.addEventListener("scroll",d,{passive:!0});let b=typeof ResizeObserver>"u"?void 0:new ResizeObserver(d);return b?.observe(p),()=>{p.removeEventListener("scroll",d),b?.disconnect()}},[]),[i,c]}function ke(i){let{part:c}=i,h=ve(i.shown,c.field,c.ids),[p,d]=ye();return e("div",{className:"creator-strip",ref:p,"data-more":d,children:c.ids.map((b,S)=>e(ne,{data:{[`data-${c.field}`]:b},spec:h[S],size:44,label:x(c.field,b),pressed:i.draft[c.field]===b,onPick:()=>i.onPick(b)},b))})}function H(i){return e("button",{type:"button",className:"creator-swatch","aria-pressed":i.pressed,"aria-label":i.label,title:i.label,style:{background:i.background},onClick:i.onPick,...i.data})}function N(i){return n("div",{className:"creator-field","data-field":i.field,children:[i.title?e("h3",{children:i.title}):null,i.children,i.error?e("p",{className:"field-error",role:"alert",children:i.error}):null]})}function $e(i){Y("creator",ae);let[c,h]=r(i.character),p=Fr(c),[d,b]=r({}),[S,P]=r(!1),[I,se]=r("idle"),[_,T]=r(),[z,j]=r(),U=Qe("(max-width: 719px)"),G=a(null),F=L(),le=Je(c),ce=Sn(),A=re(c)!==re(i.character);o(()=>h(i.character),[i.character]),o(()=>{if(!A||i.onPick)return;let s=(v)=>v.preventDefault(),m=(v)=>{if(v.button!==0||v.metaKey||v.ctrlKey||v.shiftKey||v.altKey)return;let E=v.target instanceof Element?v.target.closest("a"):null;if(!E||E.target||E.hasAttribute("download"))return;if(G.current?.contains(E))return;v.preventDefault(),v.stopPropagation(),T({kind:"link",anchor:E})},B=xi((v)=>T({kind:"navigate",proceed:v}));return window.addEventListener("beforeunload",s),document.addEventListener("click",m,!0),()=>{B(),window.removeEventListener("beforeunload",s),document.removeEventListener("click",m,!0)}},[A,i.onPick]);let w=(s)=>{b({}),h((m)=>Ri(s,m))},de=async()=>{if(i.onPick){i.onPick(M(c),c);return}P(!0),b({});try{let s=await y.setCharacter(i.name,M(c));i.onSaved?.(s),f(t("Saved."))}catch(s){let m=s instanceof fe&&s.field?s.field.split(".")[0]:void 0,B=m&&pe.has(m)?m:"*";b({[B]:l(s)})}finally{P(!1)}},me=async()=>{let s=_;if(h(i.character),b({}),s?.kind==="link")setTimeout(()=>s.anchor.click(),0);else if(s?.kind==="navigate")s.proceed()},J=e(N,{field:"class",title:t("Class"),error:d.class,children:e("div",{className:"creator-grid",children:je.map((s)=>e(Ce,{id:s,base:p,pressed:c.class===s,onPick:()=>w({class:s,colors:c.colors})},s))})}),V=n(N,{field:"colors",title:t("Colors"),error:d.colors??d.palette,children:[n("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Palette")}),oe.map((s)=>{let m=W.palettes[s];return e(H,{data:{"data-palette":s},label:x("palette",s),pressed:c.palette===s,background:`linear-gradient(135deg, ${m.primary} 52%, ${m.trim} 52%)`,onPick:()=>w({palette:s})},s)})]}),e("div",{className:"creator-custom",children:["primary","secondary","trim"].map((s)=>n("label",{children:[e("span",{children:s==="primary"?t("Primary (the accent)"):s==="secondary"?t("Secondary"):t("Trim")}),e("input",{type:"color",value:c.colors[s],onChange:(m)=>w({colors:{[s]:m.target.value}})})]},s))})]}),q=K.map((s)=>e(N,{field:s.field,title:t(s.title),error:d[s.field],children:e(ke,{part:s,draft:c,shown:p,onPick:(m)=>w({[s.field]:m})})},s.field)),Q=n(g,{children:[e(N,{field:"build",title:t("Build"),error:d.build,children:n("fieldset",{className:"segmented",children:[e("legend",{className:"visually-hidden",children:t("Build")}),["slim","regular","stout"].map((s)=>n("label",{"data-checked":c.build===s,children:[e("input",{type:"radio",name:`${F}-build`,checked:c.build===s,onChange:()=>w({build:s})}),ie(s)]},s))]})}),e(N,{field:"skin",title:t("Skin tone"),error:d.skin,children:n("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Skin tone")}),(ce??W.skins).map((s,m)=>e(H,{data:{"data-skin":String(m)},label:t("Skin tone {n}",{n:m+1}),pressed:c.skin===m,background:s,onPick:()=>w({skin:m})},m))]})}),e(N,{field:"hairColor",title:t("Hair color"),error:d.hairColor,children:n("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Hair color")}),W.hairColors.map((s,m)=>e(H,{data:{"data-hair-color":s},label:t("Hair color {n}",{n:m+1}),pressed:c.hairColor===s,background:s,onPick:()=>w({hairColor:s})},s)),e("input",{type:"color","aria-label":t("Hair color"),value:c.hairColor,onChange:(s)=>w({hairColor:s.target.value})})]})})]}),O=[{id:"class",title:t("Class"),summary:x("class",c.class),error:d.class,node:J},{id:"colors",title:t("Colors"),summary:c.palette?x("palette",c.palette):c.colors.primary,error:d.colors??d.palette,node:V},...K.map((s,m)=>({id:s.field,title:t(s.title),summary:x(s.field,c[s.field]),error:d[s.field],node:q[m]})),{id:"body",title:t("Body"),summary:ie(c.build),error:d.build??d.skin??d.hairColor,node:Q}];return n("form",{ref:G,className:"creator",style:le,"aria-labelledby":F,onSubmit:(s)=>{if(s.preventDefault(),(A||i.onPick)&&!S)de()},children:[e("h2",{id:F,className:"visually-hidden",children:t("Character of {name}",{name:i.displayName})}),n("div",{className:"creator-preview",children:[e(he,{spec:c,name:i.name,state:I,size:176,label:t("{name}, {state}",{name:i.displayName,state:t(te[I])})}),e("button",{type:"button",className:"secondary","data-action":"cycle",onClick:()=>se((s)=>Jt[(Jt.indexOf(s)+1)%Jt.length]??"idle"),children:t("Preview states")}),e("span",{className:"meta","aria-live":"polite",children:t(te[I])})]}),n("div",{className:"creator-body",children:[d["*"]?e("p",{className:"field-error",role:"alert","data-field":"*",children:d["*"]}):null,U?e("ul",{className:"creator-sections",children:O.map((s)=>e("li",{children:n("button",{type:"button",className:"creator-section secondary",onClick:()=>j(s.id),children:[e("strong",{children:s.title}),e("span",{className:"meta",children:s.summary}),s.error?e("span",{className:"field-error",role:"alert",children:s.error}):null]})},s.id))}):n(g,{children:[J,V,q,Q]}),n("div",{className:"actions creator-actions",children:[e("button",{type:"button",className:"secondary","data-action":"suggest",onClick:()=>{b({}),h(Pn({name:i.name,displayName:i.displayName,prompt:i.prompt}))},children:t("Suggest from role")}),e("button",{type:"button",className:"secondary","data-action":"randomize",onClick:()=>w(be()),children:t("Randomize")}),e("button",{type:"button",className:"secondary","data-action":"cancel",onClick:()=>{if(i.onCancel)i.onCancel();else if(A)T({kind:"cancel"})},children:t("Cancel")}),e("button",{type:"submit","data-action":"save",disabled:!A&&!i.onPick||S,children:S?t("Saving…"):i.onPick?t("Use this look"):t("Save")})]})]}),e(R,{open:U&&z!==void 0,title:O.find((s)=>s.id===z)?.title??"",onClose:()=>j(void 0),children:e("div",{className:"stack",children:O.find((s)=>s.id===z)?.node})}),e(D,{open:_!==void 0,title:t("Discard your changes?"),confirmLabel:t("Discard"),cancelLabel:t("Keep editing"),tone:"danger",onClose:()=>T(void 0),onConfirm:me,children:e("p",{children:t("The new look for {name} has not been saved.",{name:i.displayName})})})]})}var ie=(i)=>t(ge[i]);function Ce(i){let c=i.base.colors,h=C(()=>Ri({class:i.id,colors:c}),[i.id,c]);return e(ne,{data:{"data-class":i.id},spec:h,size:56,label:x("class",i.id),hint:Z(i.id),pressed:i.pressed,onPick:i.onPick})}export{$e as CharacterCreator};
|