@songsid/agend 2.2.0-alpha.8 → 2.2.0-alpha.9

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.
@@ -10,7 +10,7 @@ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-
10
10
  import { t } from "/assets/app-i18n.js";
11
11
  import { appStore, useStore } from "/assets/app-store.js";
12
12
  import { useLease } from "/assets/app-ctx.js";
13
- import { PanelHeader, onPanelKey, setTitle, addFooterItem, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
13
+ import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
14
14
  import { detailsPath } from "/assets/app-route.js";
15
15
  import { InstanceSwitch } from "/assets/instance-switch.js";
16
16
  import { HeaderTools } from "/assets/header-tools.js";
@@ -27,7 +27,7 @@ import { createChatStore } from "./chat-store.js";
27
27
  import { createThread, msgKey } from "./chat-thread.js";
28
28
  import { PreviewPanel, openPanel, panelStore, shownKey } from "./preview-panel.js";
29
29
  import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
30
- import { installTour, refreshTourSpot, startTour } from "./chat-tour.js";
30
+ import { installTour, refreshTourSpot } from "./chat-tour.js";
31
31
  import { FirstRunCard } from "./first-run.js";
32
32
  import { LightboxHost, openLightbox } from "./image-lightbox.js";
33
33
 
@@ -77,21 +77,11 @@ export function boot({ stream, boot: bootData, deps = {} }) {
77
77
  if (auth && typeof auth.sessionHandle === "function") auth.sessionHandle().then((h) => P().setBannerScope(h), () => P().setBannerScope(null));
78
78
  else P().setBannerScope(null);
79
79
  }
80
- addFooterItem("previews", PreviewOptIn);
81
- addFooterItem("tour", TourButton);
80
+ // #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
82
81
  installTour();
83
82
  return store;
84
83
  }
85
84
 
86
- function PreviewOptIn() {
87
- const [on, setOn] = useState(P() ? P().optedIn() : false);
88
- useEffect(() => (P() ? P().onChange(() => setOn(P().optedIn())) : undefined), []);
89
- return html`<label class="side-row toggle"><input type="checkbox" checked=${on} onChange=${(e) => { const want = e.target.checked; setPreviewOptIn(want).then(setOn); }} />
90
- <span>${t("chat.previews")}</span></label>`;
91
- }
92
- function TourButton() {
93
- return html`<button type="button" id="tourBtn" class="side-row" title=${t("chat.tourBtnTitle")} onClick=${startTour}><${Icon} name="info" /><span>${t("chat.tour")}</span></button>`;
94
- }
95
85
 
96
86
  // ── Code wrap: this browser's choice (agend_code_wrap). On a phone, code wraps whatever it is (app.css).
97
87
  const stored = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
@@ -12,9 +12,9 @@ import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js"
12
12
  import { t } from "/assets/app-i18n.js";
13
13
  import { appStore, useStore } from "/assets/app-store.js";
14
14
  import { useLease } from "/assets/app-ctx.js";
15
- import { PanelHeader, PendingChanges, setTitle } from "/assets/app-shell.js";
16
- import { navigate, setLeaveGuard } from "/assets/app-nav.js";
17
- import { settingsPath, SETTINGS_SECTIONS } from "/assets/app-route.js";
15
+ import { DevicePrefs, PanelHeader, PendingChanges, setTitle } from "/assets/app-shell.js";
16
+ import { navigate, navStore, setLeaveGuard } from "/assets/app-nav.js";
17
+ import { chatPath, settingsPath, SETTINGS_SECTIONS } from "/assets/app-route.js";
18
18
  import { Dialog } from "/assets/ui-dialog.js";
19
19
  import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
20
20
  import { Icon } from "/assets/ui-icons.js";
@@ -37,9 +37,13 @@ import { SetupWizard } from "./settings-wizard.js";
37
37
  import { CreateInstanceDialog } from "./panel-fleet.js";
38
38
  import { FirstRunCard, takeWizardRequest } from "./first-run.js";
39
39
  import { takeAgentRequest } from "./settings-request.js";
40
+ // #1604: This device — the preview opt-in (one path: the app's confirm, then preview.js) and the tour, from the chat.
41
+ import { setPreviewOptIn } from "./panel-chat.js";
42
+ import { startTour } from "./chat-tour.js";
43
+ import "./chat-strings.js";
40
44
 
41
45
  const tn = (k, ...v) => t(`settings.${k}`, ...v);
42
- const ICONS = { agents: "bot", bots: "plug", classic: "room", general: "sliders", advanced: "code" };
46
+ const ICONS = { device: "user", agents: "bot", bots: "plug", classic: "room", general: "sliders", advanced: "code" };
43
47
  const RELEASES = "https://github.com/songsid/AgEnD/releases";
44
48
 
45
49
  /** GET under the panel's lease → { ok, status, body }. */
@@ -218,7 +222,9 @@ export function SettingsPanel({ route, navKey }) {
218
222
  const insts = (ctx.fleet && ctx.fleet.instances) || {};
219
223
  if (own(insts, want.name)) setDialog({ kind: "agent", name: want.name, inst: insts[want.name], key: navKey });
220
224
  }, [!!ctx, navKey]);
221
- const sectionBody = !data ? html`<${Skeleton} lines=${6} />`
225
+ // #1604: This device needs no configuration: shown at once, whatever the configuration read does.
226
+ const sectionBody = section === "device" ? html`<${DeviceSection} />`
227
+ : !data ? html`<${Skeleton} lines=${6} />`
222
228
  : data.error ? html`<${ErrorState} message=${tn("configLoadFailed")} onRetry=${reload} />`
223
229
  : section === "agents" ? html`<${Agents} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
224
230
  : section === "bots" ? html`<${Bots} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
@@ -250,6 +256,49 @@ export function SettingsPanel({ route, navKey }) {
250
256
  </div>`;
251
257
  }
252
258
 
259
+ /**
260
+ * #1604: what this browser chooses for itself — moved here from the sidebar footer, each with the storage it always had:
261
+ * theme (theme.js), language (app-i18n.js), HTML previews on this device (preview.js, through the app's confirm), and
262
+ * the chat tour (replayed on the chat it points at).
263
+ */
264
+ export function DeviceSection() {
265
+ const P = () => globalThis.AgendPreview;
266
+ const [on, setOn] = useState(P() ? P().optedIn() : false);
267
+ useEffect(() => (P() ? P().onChange(() => setOn(P().optedIn())) : undefined), []);
268
+ const replay = () => {
269
+ let last = null;
270
+ try { last = localStorage.getItem("agend_last_instance"); } catch { /* none */ }
271
+ navigate(last ? chatPath(last) : "/ui");
272
+ // This replay belongs to the navigation it just made — that one, by its sequence number, not by URL (A → B → A, or
273
+ // the same path again, is a new navigation). It did not happen (a leave guard said no) or it is not the chat:
274
+ // nothing. Left meanwhile (another page, another navigation): nothing (#1609 review).
275
+ const nav = navStore.get();
276
+ if (!nav.route || nav.route.panel !== "chat") return;
277
+ const mine = nav.seq;
278
+ const current = () => navStore.get().seq === mine;
279
+ // The tour points at the chat's own controls: start it once the CHAT has drawn — its Send button, not #main (the
280
+ // shell's landmark is on every page, Settings included) — waiting at most ~3 s (#1608 review); a chat that has not
281
+ // drawn by then (its module failed, an unknown instance) gets no tour. The focus goes to the composer first: the
282
+ // tour returns it there when it ends, not to this button, which is gone.
283
+ let tries = 0;
284
+ const go = () => {
285
+ if (!current()) return;
286
+ if (!document.getElementById("sendBtn")) { if (++tries <= 30) setTimeout(go, 100); return; }
287
+ const here = document.getElementById("msgIn") || document.getElementById("main");
288
+ if (here && typeof here.focus === "function") here.focus();
289
+ if (current()) startTour();
290
+ };
291
+ setTimeout(go, 0);
292
+ };
293
+ return html`<section class="card s-device"><h3>${tn("sec_device")}</h3>
294
+ <p class="sub">${tn("deviceLead")}</p>
295
+ <${DevicePrefs} labels=${true} />
296
+ ${P() ? html`<label class="check"><input type="checkbox" checked=${on} onChange=${(e) => { const want = e.target.checked; setPreviewOptIn(want).then(setOn); }} />
297
+ <span>${t("chat.previews")}</span></label>` : null}
298
+ <div class="s-device-row"><button type="button" id="tourBtn" class="btn" title=${t("chat.tourBtnTitle")} onClick=${replay}><${Icon} name="info" size=${16} />${t("chat.tour")}</button></div>
299
+ </section>`;
300
+ }
301
+
253
302
  /** The operation in hand still owns Apply: its writes and job, or a restart of AgEnD from the press to its end. */
254
303
  const opBusy = (op) => !!op && (["writing", "posting", "watching"].includes(op.phase) || op.restart === "busy");
255
304
  function dropSecrets(staged) { for (const c of staged.values()) if (c.connectionSecret) c.connectionSecret.secret = ""; }
@@ -217,6 +217,7 @@ register("settings", {
217
217
  concurrencyWarning: "May exceed CPU capacity on smaller hosts",
218
218
  staggerRange: "Must be between 0 and 30000",
219
219
  staggerWarning: "Very low values can cause startup spikes",
220
+ sec_device: "This device", deviceLead: "Choices for this browser only — other devices keep their own.",
220
221
  sec_agents: "Agents",
221
222
  sec_bots: "Connections",
222
223
  sec_classic: "ClassicBot",
@@ -561,6 +562,7 @@ register("settings", {
561
562
  concurrencyWarning: "在較小的主機上可能超出 CPU 負荷",
562
563
  staggerRange: "必須介於 0 到 30000",
563
564
  staggerWarning: "數值太低可能造成啟動尖峰",
565
+ sec_device: "這台裝置", deviceLead: "只套用在這個瀏覽器,其他裝置保留各自的設定。",
564
566
  sec_agents: "Agent",
565
567
  sec_bots: "連線",
566
568
  sec_classic: "ClassicBot",
@@ -45,12 +45,11 @@ register("app", {
45
45
  needsYou: "needs you", approxNote: "Read from the terminal, so approximate", chat: "Chat", details: "Details", instanceViews: "Views of {0}",
46
46
  textSize: "Text size: {0}", textSize_s: "S", textSize_m: "M", textSize_l: "L", textSize_fit: "Fit", usage: "Usage",
47
47
  statusRunning: "running", statusStopped: "stopped", statusCrashed: "crashed", statusWorking: "working", statusStuck: "looks stuck", statusIdle: "idle",
48
- connPolling: "Live updates paused — refreshing every 5 s", connDown: "Disconnected — trying again…", connConnecting: "Connecting…",
49
48
  connReconnecting: "Can't reach AgEnD — it may be restarting. Reconnecting…", connReconnectingIn: "Can't reach AgEnD — it may be restarting. Trying again in {0} s.",
50
49
  hydrating: "Loading what is open now…", hydrateFailed: "Could not load what is open now (prompts, ticks).",
51
50
  skip: "Skip to the conversation", loadFailed: "This could not be loaded.", retry: "Try again", loading: "Loading…",
52
51
  noInstances: "No instances yet", noInstancesHint: "Create one to start chatting.",
53
- session: "Session", signIn: "Sign in", signedIn: "Signed in", signedOut: "Not signed in", thisDevice: "This device",
52
+ session: "Session", signIn: "Sign in", signedIn: "Signed in", signedOut: "Not signed in", thisDevice: "This device", devicePrefs: "Theme and language",
54
53
  lastActive: "active {0}", justNow: "just now", minAgo: "{0} min ago", hrAgo: "{0} h ago", signOut: "Sign out",
55
54
  signOutAll: "Sign out everywhere", devices: "Signed-in devices", expires: "Ends by {0} at the latest, or after {1} unused",
56
55
  failed: "That did not work — try again.", close: "Close", more: "More actions", menu: "Menu",
@@ -75,12 +74,11 @@ register("app", {
75
74
  needsYou: "等你回應", approxNote: "從終端畫面判讀,僅供參考", chat: "聊天", details: "詳細資訊", instanceViews: "{0} 的檢視",
76
75
  textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "剛好填滿", usage: "用量",
77
76
  statusRunning: "執行中", statusStopped: "已停止", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
78
- connPolling: "即時更新暫停中——每 5 秒重新整理", connDown: "已斷線——正在重試…", connConnecting: "連線中…",
79
77
  connReconnecting: "連不上 AgEnD——可能正在重新啟動。正在重新連線…", connReconnectingIn: "連不上 AgEnD——可能正在重新啟動。{0} 秒後再試。",
80
78
  hydrating: "正在載入目前的狀態…", hydrateFailed: "無法載入目前的狀態(提示、送達標記)。",
81
79
  skip: "跳到對話", loadFailed: "無法載入。", retry: "再試一次", loading: "載入中…",
82
80
  noInstances: "還沒有 instance", noInstancesHint: "建立一個就能開始聊天。",
83
- session: "登入狀態", signIn: "登入", signedIn: "已登入", signedOut: "尚未登入", thisDevice: "這台裝置",
81
+ session: "登入狀態", signIn: "登入", signedIn: "已登入", signedOut: "尚未登入", thisDevice: "這台裝置", devicePrefs: "主題與語言",
84
82
  lastActive: "{0}活動", justNow: "剛剛", minAgo: "{0} 分鐘前", hrAgo: "{0} 小時前", signOut: "登出",
85
83
  signOutAll: "全部登出", devices: "已登入的裝置", expires: "最晚 {0} 結束;閒置 {1} 也會結束",
86
84
  failed: "沒有成功,請再試一次。", close: "關閉", more: "更多動作", menu: "選單",
@@ -2,7 +2,7 @@
2
2
  // a test runs both over one list of cases). Pure: no DOM, no history.
3
3
 
4
4
  export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "config"];
5
- export const SETTINGS_SECTIONS = ["agents", "bots", "classic", "general", "advanced"];
5
+ export const SETTINGS_SECTIONS = ["device", "agents", "bots", "classic", "general", "advanced"]; // #1604: device first
6
6
  const MAX_INSTANCE_NAME = 128;
7
7
  const PATH_SEPARATOR_OR_CONTROL = /[/\\\u0000-\u001f\u007f]/;
8
8
 
@@ -14,7 +14,7 @@ import { ErrorState, Skeleton } from "./ui-states.js";
14
14
  import { InstanceNav, RosterNav } from "./instance-nav.js";
15
15
 
16
16
  /** Shell state shared with panels: the phone drawer, the collapsed sidebar, extensions a panel module adds. */
17
- export const shellStore = createStore({ drawer: false, collapsed: readCollapsed(), footer: [], keyboard: false, dialog: null, side: null });
17
+ export const shellStore = createStore({ drawer: false, collapsed: readCollapsed(), keyboard: false, dialog: null, side: null });
18
18
 
19
19
  /**
20
20
  * The mounted panel's own sidebar section, in place of the instance list (View's roster: its groups, filter and order,
@@ -49,10 +49,6 @@ export function toggleSidebar() {
49
49
  shellStore.set({ collapsed });
50
50
  try { localStorage.setItem("agend_sidebar", collapsed ? "hidden" : "shown"); } catch { /* this page only */ }
51
51
  }
52
- /** A panel module adds a control to the sidebar footer (the chat adds the preview opt-in and the tour). */
53
- export function addFooterItem(key, Component) {
54
- shellStore.set(s => ({ ...s, footer: [...s.footer.filter(x => x.key !== key), { key, Component }] }));
55
- }
56
52
 
57
53
  // ── Keys ── One document listener for the page. Esc closes what is on top first (the drawer); then the panel's own
58
54
  // handlers run — only for the mounted panel, and only when the focus is inside it or on nothing (the body).
@@ -125,8 +121,8 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
125
121
  <nav class="side-nav" aria-label=${t("app.menu")}>${navLink("view", "/view", "view", t("app.view"))}</nav>
126
122
  ${shell.side ? html`<${shell.side.Component} />` : html`<${ViewOnlyNav} route=${route} />`}
127
123
  <div class="side-foot">
128
- <${Prefs} />
129
124
  <a class="side-row" href=${signInHref()}><${Icon} name="user" /><span>${t("app.signIn")}</span></a>
125
+ <${PrefsPopover} />
130
126
  </div>
131
127
  </aside>`;
132
128
  }
@@ -145,8 +141,6 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
145
141
  exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />`}
146
142
  <div class="side-foot">
147
143
  ${navLink("settings", settingsPath(), "settings", t("app.settings"))}
148
- ${shell.footer.map(({ key, Component }) => html`<${Component} key=${key} />`)}
149
- <${Prefs} />
150
144
  <${SessionMenu} />
151
145
  </div>
152
146
  </aside>`;
@@ -157,19 +151,31 @@ function ViewOnlyNav({ route }) {
157
151
  return html`<${RosterNav} route=${route} viewOnly=${true} onPick=${closeDrawer} />`;
158
152
  }
159
153
 
160
- function Prefs() {
154
+ /**
155
+ * This browser's theme and language (#1604: Settings → This device; for the anonymous View reader, the footer's
156
+ * popover). The same stores as ever: AgendTheme (theme.js) and setLang (app-i18n.js) — only where they are shown moved.
157
+ */
158
+ export function DevicePrefs({ labels = false } = {}) {
161
159
  const [theme, setTheme] = useState(typeof AgendTheme !== "undefined" ? AgendTheme.get() : "system");
162
160
  const [l, setL] = useState(lang());
163
- return html`<div class="prefs">
164
- <label class="pref"><${Icon} name="sun" size=${16} /><span class="sr-only">${t("app.theme")}</span>
161
+ return html`<div class=${`prefs${labels ? " labelled" : ""}`}>
162
+ <label class="pref"><${Icon} name="sun" size=${16} /><span class=${labels ? "pref-label" : "sr-only"}>${t("app.theme")}</span>
165
163
  <select value=${theme} aria-label=${t("app.theme")} onChange=${(e) => { setTheme(e.target.value); if (typeof AgendTheme !== "undefined") AgendTheme.set(e.target.value); }}>
166
164
  <option value="system">${t("app.themeSystem")}</option><option value="light">${t("app.themeLight")}</option><option value="dark">${t("app.themeDark")}</option></select></label>
167
- <label class="pref"><${Icon} name="globe" size=${16} /><span class="sr-only">${t("app.language")}</span>
165
+ <label class="pref"><${Icon} name="globe" size=${16} /><span class=${labels ? "pref-label" : "sr-only"}>${t("app.language")}</span>
168
166
  <select value=${l} aria-label=${t("app.language")} onChange=${(e) => { setL(e.target.value); setLang(e.target.value); }}>
169
167
  <option value="en">English</option><option value="zh-TW">中文</option></select></label>
170
168
  </div>`;
171
169
  }
172
170
 
171
+ /** #1604: the anonymous View reader has no Settings: theme and language from one compact control beside Sign in. */
172
+ function PrefsPopover() {
173
+ return html`<details class="prefs-pop">
174
+ <summary class="icon-btn" aria-label=${t("app.devicePrefs")} title=${t("app.devicePrefs")}><${Icon} name="sun" /></summary>
175
+ <div class="pop" role="group" aria-label=${t("app.devicePrefs")}><${DevicePrefs} labels=${true} /></div>
176
+ </details>`;
177
+ }
178
+
173
179
  /**
174
180
  * The header every panel shows: ☰ (phone, or a collapsed sidebar), its title and status, then its actions. `nav`
175
181
  * (#1523 N2): an instance page's view switch — beside the title on a wide screen, a row of tabs under it on a phone.
@@ -228,9 +234,10 @@ function ConnectionLine() {
228
234
  ${hydration === "failed" && retryHydration ? html` <button type="button" class="btn btn-sm" onClick=${() => retryHydration()}>${t("app.retry")}</button>` : null}</div>`;
229
235
  }
230
236
  if (connection === "reconnecting") return html`<${Reconnecting} />`;
231
- if (connection === "live" || connection === "none" || connection === "ended") return null; // ended: agend-auth's banner
232
- const text = connection === "polling" ? t("app.connPolling") : connection === "down" ? t("app.connDown") : null;
233
- return text ? html`<div class="conn" role="status">${text}</div>` : null;
237
+ // #1603: polling is a working transport (the public link polls by design; a buffering proxy makes a page poll), not
238
+ // a fault — messages still arrive, at most ~5 s late, and there is nothing to do: no strip. Live, none, and ended
239
+ // (agend-auth's banner says that) draw nothing either; only reconnecting and a failed catch-up are shown.
240
+ return null;
234
241
  }
235
242
  /** #1580: the fleet is not answering (a restart): said, with when the next try is — never a silent stale page. */
236
243
  function Reconnecting() {
@@ -0,0 +1,89 @@
1
+ // #1605: on a phone (whenever the sidebar is a drawer), a swipe right in the main area opens the drawer and a swipe left
2
+ // on the drawer or its scrim closes it — through the same open/close as ☰, so focus, the scrim and Esc are unchanged.
3
+ //
4
+ // Never in the way of anything that owns a sideways drag or a touch:
5
+ // - the left screen edge (the first EDGE px) belongs to the browser: on iOS Safari a swipe from there is "back";
6
+ // - a gesture that starts in something that scrolls sideways (a code block, a wide table or CSV card — found as the
7
+ // nearest horizontally scrollable ancestor, not a fixed list), in the terminal view (xterm), in the composer or any
8
+ // input, or while a dialog, the image lightbox or the preview panel is open, is left alone;
9
+ // - vertical scrolling is never taken: only a mostly-horizontal swipe counts (dx ≥ MIN_DX and |dy| < |dx|/2), and the
10
+ // listeners are passive — nothing is ever prevented, the browser scrolls as it always does.
11
+
12
+ export const SWIPE = { EDGE: 24, MIN_DX: 60 };
13
+
14
+ /** Elements a sideways gesture belongs to (their own scrolling, selection, typing or drag). */
15
+ function owns(el) {
16
+ const tag = (el.tagName || "").toLowerCase();
17
+ if (tag === "input" || tag === "textarea" || tag === "select" || tag === "pre" || tag === "table" || tag === "iframe") return true;
18
+ if (el.isContentEditable || (el.getAttribute && el.getAttribute("contenteditable") === "true")) return true;
19
+ const cl = el.classList;
20
+ return !!cl && (cl.contains("xterm") || cl.contains("composer") || cl.contains("composer-box") || cl.contains("pv-panel")
21
+ || cl.contains("lb") || cl.contains("csv-card") || cl.contains("codeblock"));
22
+ }
23
+ /** Scrolls sideways (and has room to): the gesture is the content's. */
24
+ function scrollsSideways(el, win) {
25
+ if (!(el.scrollWidth > el.clientWidth + 1)) return false;
26
+ const cs = win && typeof win.getComputedStyle === "function" ? win.getComputedStyle(el) : null;
27
+ const ox = cs ? cs.overflowX : "";
28
+ return ox === "auto" || ox === "scroll";
29
+ }
30
+ function blocked(target, win) {
31
+ for (let el = target; el && el.nodeType === 1; el = el.parentNode) {
32
+ if (owns(el) || scrollsSideways(el, win)) return true;
33
+ }
34
+ return false;
35
+ }
36
+ function inside(target, test) {
37
+ for (let el = target; el && el.nodeType === 1; el = el.parentNode) if (test(el)) return true;
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * Install once per page. `isDrawer()`: the sidebar is a drawer now (phone width); `isOpen()`; `open()` / `close()`: the
43
+ * same paths ☰ and the scrim use. Returns the uninstall function.
44
+ */
45
+ export function installSwipe({ doc = document, win = window, isDrawer, isOpen, open, close }) {
46
+ let start = null; // { x, y, opening }
47
+ const modalUp = () => !!(doc.querySelector && (doc.querySelector("dialog[open]") || doc.querySelector(".pv-panel")))
48
+ || !!(doc.documentElement && doc.documentElement.classList && doc.documentElement.classList.contains("lb-open"));
49
+ function onStart(e) {
50
+ start = null;
51
+ if (!e.touches || e.touches.length !== 1 || !isDrawer()) return;
52
+ const t = e.touches[0], target = e.target;
53
+ if (modalUp() || blocked(target, win)) return;
54
+ if (isOpen()) {
55
+ // Closing: a swipe left that starts on the drawer or its scrim.
56
+ if (inside(target, (el) => el.id === "sidebar" || (el.classList && el.classList.contains("scrim")))) start = { x: t.clientX, y: t.clientY, opening: false };
57
+ return;
58
+ }
59
+ // Opening: in the main area, and never from the edge the browser owns.
60
+ if (t.clientX < SWIPE.EDGE) return;
61
+ if (!inside(target, (el) => el.id === "main")) return;
62
+ start = { x: t.clientX, y: t.clientY, opening: true };
63
+ }
64
+ function onEnd(e) {
65
+ const s = start;
66
+ start = null;
67
+ if (!s || !e.changedTouches || !e.changedTouches.length || !isDrawer()) return;
68
+ // A dialog, the lightbox or the preview panel that came up during the gesture still owns it (#1609 review).
69
+ if (modalUp()) return;
70
+ const t = e.changedTouches[0];
71
+ const dx = t.clientX - s.x, dy = t.clientY - s.y;
72
+ if (Math.abs(dx) < SWIPE.MIN_DX || Math.abs(dy) >= Math.abs(dx) / 2) return; // short, or mostly vertical: a scroll
73
+ if (s.opening && dx > 0 && !isOpen()) open();
74
+ else if (!s.opening && dx < 0 && isOpen()) close();
75
+ }
76
+ function onCancel() { start = null; }
77
+ function onMove(e) { if (start && e.touches && e.touches.length !== 1) start = null; } // a second finger: a pinch
78
+ const opts = { passive: true };
79
+ doc.addEventListener("touchstart", onStart, opts);
80
+ doc.addEventListener("touchmove", onMove, opts);
81
+ doc.addEventListener("touchend", onEnd, opts);
82
+ doc.addEventListener("touchcancel", onCancel, opts);
83
+ return () => {
84
+ doc.removeEventListener("touchstart", onStart, opts);
85
+ doc.removeEventListener("touchmove", onMove, opts);
86
+ doc.removeEventListener("touchend", onEnd, opts);
87
+ doc.removeEventListener("touchcancel", onCancel, opts);
88
+ };
89
+ }
@@ -108,7 +108,22 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
108
108
  flex-shrink: 0; font-size: 11px; line-height: 16px; font-weight: var(--fw-semibold); padding: 0 var(--s-2); border-radius: var(--r-full);
109
109
  background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-line); white-space: nowrap;
110
110
  }
111
- .side-foot { display: flex; flex-direction: column; gap: 1px; padding: var(--s-2); }
111
+ /* #1604: one row — Settings and Session (the anonymous View reader: Sign in and the theme/language popover). Their
112
+ pop-ups open over the whole footer's width. */
113
+ .side-foot { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-2); position: relative; }
114
+ .side-foot > .side-row, .side-foot > .session { flex: 1; min-width: 0; }
115
+ .side-foot > .session { position: static; }
116
+ .side-foot .side-row span:not(.dot):not(.meta) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
117
+ .side-foot .session .meta { display: none; }
118
+ .prefs-pop > summary { list-style: none; cursor: pointer; }
119
+ .prefs-pop > summary::-webkit-details-marker { display: none; }
120
+ .prefs.labelled { flex-direction: column; gap: var(--s-2); padding: 0; }
121
+ .prefs.labelled .pref { flex: none; gap: var(--s-2); color: var(--text); }
122
+ .prefs.labelled .pref-label { min-width: 7em; font-size: var(--fs-sm); }
123
+ .prefs.labelled .pref select { flex: 0 1 14em; height: 34px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: var(--fs-sm); padding: 0 var(--s-2); }
124
+ .s-device { display: flex; flex-direction: column; gap: var(--s-3); }
125
+ .s-device .sub { margin: 0; color: var(--text-3); font-size: var(--fs-sm); }
126
+ .s-device-row { display: flex; gap: var(--s-2); }
112
127
  .prefs { display: flex; gap: var(--s-2); padding: var(--s-1) var(--s-3); }
113
128
  .pref { display: flex; align-items: center; gap: var(--s-1); color: var(--text-2); flex: 1; min-width: 0; }
114
129
  .pref select { flex: 1; min-width: 0; height: 30px; border: 0; border-radius: var(--r-sm); background: transparent; color: var(--text-2); font-size: var(--fs-xs); cursor: pointer; }
@@ -6,7 +6,8 @@
6
6
  // reached only by the dynamic imports below, in "full" mode. A test walks the static imports over real HTTP and asserts
7
7
  // none of them names /ui/.
8
8
  import { html, render } from "./app-html.js";
9
- import { Shell, handleKey, showDialog, closeDrawer, retryable, setNewInstanceOpener } from "./app-shell.js";
9
+ import { Shell, handleKey, showDialog, closeDrawer, openDrawer, shellStore, retryable, setNewInstanceOpener } from "./app-shell.js";
10
+ import { installSwipe } from "./app-swipe.js";
10
11
  import { startRouter, navStore, navigate } from "./app-nav.js";
11
12
  import { createStream } from "./app-stream.js";
12
13
  import { appStore, applyStatus, applyActivity } from "./app-store.js";
@@ -97,5 +98,12 @@ startRouter(window);
97
98
  }
98
99
 
99
100
  document.addEventListener("keydown", handleKey);
101
+ // #1605: on a phone (the sidebar is a drawer), swipe right in the main area to open it, left to close it — the same
102
+ // open/close as ☰ and the scrim. Nothing that owns a sideways drag (code, tables, the terminal, inputs, dialogs) and
103
+ // never the left edge (iOS: "back").
104
+ installSwipe({
105
+ isDrawer: () => typeof matchMedia === "function" && matchMedia("(max-width: 899px)").matches,
106
+ isOpen: () => shellStore.get().drawer, open: openDrawer, close: closeDrawer,
107
+ });
100
108
 
101
109
  render(html`<${Shell} panels=${panels} onNewInstance=${onNewInstance} viewOnly=${mode === "view-only"} />`, document.getElementById("app"));
@@ -12,7 +12,7 @@
12
12
  */
13
13
  export declare const FLEET_TABS: readonly ["tasks", "schedules", "teams", "org", "cache", "config"];
14
14
  export type FleetTab = typeof FLEET_TABS[number];
15
- export declare const SETTINGS_SECTIONS: readonly ["agents", "bots", "classic", "general", "advanced"];
15
+ export declare const SETTINGS_SECTIONS: readonly ["device", "agents", "bots", "classic", "general", "advanced"];
16
16
  export type SettingsSection = typeof SETTINGS_SECTIONS[number];
17
17
  export type ShellRoute = {
18
18
  panel: "chat";
@@ -11,7 +11,8 @@
11
11
  * Settings panel (step 3; settings-api.ts serves them, signed in only).
12
12
  */
13
13
  export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "config"];
14
- export const SETTINGS_SECTIONS = ["agents", "bots", "classic", "general", "advanced"];
14
+ // #1604: "device" — this browser's own choices (theme, language, HTML previews, the tour); no configuration behind it.
15
+ export const SETTINGS_SECTIONS = ["device", "agents", "bots", "classic", "general", "advanced"];
15
16
  const PATH_SEPARATOR_OR_CONTROL = /[/\\\u0000-\u001f\u007f]/;
16
17
  /** Longest instance name a path may carry; real names are far shorter. */
17
18
  export const MAX_INSTANCE_NAME = 128;
@@ -1 +1 @@
1
- {"version":3,"file":"web-shell-routes.js","sourceRoot":"","sources":["../src/web-shell-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web-shell-routes.js","sourceRoot":"","sources":["../src/web-shell-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAC;AAE7F,uHAAuH;AACvH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AAezG,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AAC7D,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,iBAAiB,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;WAC9F,IAAI,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,2EAA2E;AAC3E,SAAS,aAAa,CAAC,OAAe;IACpC,IAAI,CAAC;QAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACpE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,IAAY;IACrD,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;IACvF,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;IAC5F,8GAA8G;IAC9G,wCAAwC;IACxC,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;IAC9E,8GAA8G;IAC9G,MAAM,OAAO,GAAG,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;QACxC,OAAO,IAAI,KAAK,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC5I,CAAC;IACD,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,KAAK;QAAE,OAAQ,UAAgC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACzJ,MAAM,IAAI,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;QACrC,OAAO,IAAI,KAAK,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACzI,CAAC;IACD,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;IACzF,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;QACrC,OAAO,IAAI,KAAK,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACzI,CAAC;IACD,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpG,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAQ,iBAAuC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;YACpE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACtG,CAAC;IACD,sGAAsG;IACtG,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,UAAU,CAAC;AAC1E,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;AACvG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC;AAC1C,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,OAAO,YAAY,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACpD,CAAC;AACD,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,OAAO,SAAS,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACjD,CAAC;AACD,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,OAAO,mBAAmB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC3D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.2.0-alpha.8",
3
+ "version": "2.2.0-alpha.9",
4
4
  "description": "Multi-agent fleet daemon — run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
5
5
  "type": "module",
6
6
  "bin": {