@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.21
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/README.md +7 -1
- package/README.zh-TW.md +7 -1
- package/dist/agent-cli-instructions.md +9 -2
- package/dist/agent-cli.js +63 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +4 -1
- package/dist/agent-file-read.js +189 -16
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +29 -5
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +45 -13
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +120 -0
- package/dist/authority/web-authority.js +157 -0
- package/dist/authority/web-authority.js.map +1 -0
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +18 -8
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +8 -0
- package/dist/backend/codex-metadata.js +74 -17
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.js +15 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +2 -0
- package/dist/backend/credential-profile.js +2 -0
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js +15 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +23 -3
- package/dist/backend-command.js +25 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bug-report-cli.d.ts +30 -0
- package/dist/bug-report-cli.js +138 -0
- package/dist/bug-report-cli.js.map +1 -0
- package/dist/bug-report.d.ts +47 -0
- package/dist/bug-report.js +136 -0
- package/dist/bug-report.js.map +1 -0
- package/dist/bundled-skills.d.ts +14 -0
- package/dist/bundled-skills.js +29 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +55 -3
- package/dist/cache-service.js +152 -16
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
- package/dist/channel/adapters/discord-client-retirement.js +57 -0
- package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +9 -0
- package/dist/channel/adapters/discord.js +49 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +22 -11
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +6 -0
- package/dist/classic-channel-manager.d.ts +10 -5
- package/dist/classic-channel-manager.js +12 -9
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +224 -111
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +6 -0
- package/dist/config-validator.js +129 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +13 -1
- package/dist/config.js +14 -5
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +7 -0
- package/dist/cross-instance-notice.js +10 -0
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +24 -1
- package/dist/daemon.js +151 -19
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/delivery-outbox.d.ts +10 -0
- package/dist/delivery-outbox.js +17 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/diagnostic-redaction.d.ts +10 -0
- package/dist/diagnostic-redaction.js +167 -0
- package/dist/diagnostic-redaction.js.map +1 -0
- package/dist/fleet-context.d.ts +10 -0
- package/dist/fleet-manager.d.ts +505 -15
- package/dist/fleet-manager.js +1870 -204
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +18 -0
- package/dist/fleet-process-handlers.js +69 -0
- package/dist/fleet-process-handlers.js.map +1 -0
- package/dist/fleet-traffic.d.ts +74 -0
- package/dist/fleet-traffic.js +196 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +17 -6
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
- package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +190 -47
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +545 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +24 -1
- package/dist/human-delivery-receipts.js +64 -11
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-config-impact.js +7 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -1
- package/dist/instance-lifecycle.js +79 -18
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +12 -1
- package/dist/instructions.js +13 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +173 -12
- package/dist/locale.js.map +1 -1
- package/dist/log-steps.d.ts +1 -0
- package/dist/log-steps.js +15 -0
- package/dist/log-steps.js.map +1 -1
- package/dist/login-controller.d.ts +45 -0
- package/dist/login-controller.js +101 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +96 -0
- package/dist/ls-attach-resolve.js +180 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/ls-rows.d.ts +12 -0
- package/dist/ls-rows.js +82 -3
- package/dist/ls-rows.js.map +1 -1
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/needs-you.d.ts +10 -0
- package/dist/needs-you.js +2 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +11 -0
- package/dist/outbound-handlers.js +139 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +200 -4
- package/dist/outbound-schemas.js +124 -11
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +17 -4
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +19 -1
- package/dist/quickstart-api.js +94 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +4 -0
- package/dist/reply-buttons-controller.js +2 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/restart-progress.d.ts +80 -1
- package/dist/restart-progress.js +306 -21
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/scheduler/db.d.ts +40 -13
- package/dist/scheduler/db.js +123 -89
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/task-board-text.d.ts +26 -0
- package/dist/scheduler/task-board-text.js +135 -0
- package/dist/scheduler/task-board-text.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +650 -0
- package/dist/scheduler/task-board.js +1938 -0
- package/dist/scheduler/task-board.js.map +1 -0
- package/dist/scheduler/types.d.ts +6 -1
- package/dist/search/search-docs.d.ts +58 -0
- package/dist/search/search-docs.js +63 -0
- package/dist/search/search-docs.js.map +1 -0
- package/dist/search/search-index.d.ts +141 -0
- package/dist/search/search-index.js +440 -0
- package/dist/search/search-index.js.map +1 -0
- package/dist/search/search-store.d.ts +122 -0
- package/dist/search/search-store.js +236 -0
- package/dist/search/search-store.js.map +1 -0
- package/dist/search/search-worker.d.ts +1 -0
- package/dist/search/search-worker.js +163 -0
- package/dist/search/search-worker.js.map +1 -0
- package/dist/service-installer.js +12 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +23 -0
- package/dist/settings-api.js +164 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +104 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -1
- package/dist/settings-confirmation.js +2 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +34 -3
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +11 -0
- package/dist/settings-http-confirmation.js +10 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +23 -7
- package/dist/setup-form.js.map +1 -1
- package/dist/spawn-gate.d.ts +1 -0
- package/dist/spawn-gate.js +13 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/task-board-tool.d.ts +25 -0
- package/dist/task-board-tool.js +154 -0
- package/dist/task-board-tool.js.map +1 -0
- package/dist/task-board-waker.d.ts +44 -0
- package/dist/task-board-waker.js +128 -0
- package/dist/task-board-waker.js.map +1 -0
- package/dist/tmux-exec-helper.d.ts +94 -0
- package/dist/tmux-exec-helper.js +407 -0
- package/dist/tmux-exec-helper.js.map +1 -0
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +42 -11
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +0 -7
- package/dist/tool-permissions.js +44 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -1
- package/dist/topic-commands.js +45 -19
- package/dist/topic-commands.js.map +1 -1
- package/dist/tunnel/cloudflared.js +37 -2
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/types.d.ts +3 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +63 -1
- package/dist/ui/app-effects.js +90 -32
- package/dist/ui/backend-signin.js +297 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +8 -8
- package/dist/ui/chat-effects.js +110 -15
- package/dist/ui/chat-msg-actions.js +176 -0
- package/dist/ui/chat-render.js +21 -3
- package/dist/ui/chat-store.js +64 -12
- package/dist/ui/chat-strings.js +85 -8
- package/dist/ui/chat-thread.js +204 -21
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/effect-trial-panel.js +19 -16
- package/dist/ui/effects-waker.js +70 -0
- package/dist/ui/fleet-canvas-layout.js +117 -0
- package/dist/ui/fleet-canvas.js +555 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-route-scene.js +207 -0
- package/dist/ui/fleet-route-worker.js +21 -0
- package/dist/ui/fleet-route.js +88 -0
- package/dist/ui/fleet-task-card.js +436 -0
- package/dist/ui/fleet-task-common.js +448 -0
- package/dist/ui/fleet-tasks.js +535 -0
- package/dist/ui/fleet-traffic.js +359 -0
- package/dist/ui/fleet-usage.js +435 -0
- package/dist/ui/panel-chat.js +264 -66
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +65 -83
- package/dist/ui/panel-needs.js +4 -2
- package/dist/ui/panel-settings.js +56 -11
- package/dist/ui/preview.js +3 -2
- package/dist/ui/search-dialog.js +349 -0
- package/dist/ui/settings-appearance.js +44 -19
- package/dist/ui/settings-apply.js +3 -3
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-confirm.js +7 -2
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +4 -3
- package/dist/ui/settings-guests.js +117 -0
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +15 -6
- package/dist/ui/settings-mods.js +23 -9
- package/dist/ui/settings-owner-effects.js +15 -13
- package/dist/ui/settings-strings.js +122 -42
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +1 -1
- package/dist/ui/shared/app-i18n.js +11 -2
- package/dist/ui/shared/app-prefs.js +17 -4
- package/dist/ui/shared/app-route.js +19 -1
- package/dist/ui/shared/app-session.js +2 -0
- package/dist/ui/shared/app-shell.js +174 -30
- package/dist/ui/shared/app-store.js +26 -1
- package/dist/ui/shared/app-stream.js +5 -1
- package/dist/ui/shared/app-themes.js +140 -7
- package/dist/ui/shared/app.css +705 -20
- package/dist/ui/shared/app.js +26 -15
- package/dist/ui/shared/card-strings.js +8 -0
- package/dist/ui/shared/effect-app-runtime.js +68 -19
- package/dist/ui/shared/effect-chat-runtime.js +16 -3
- package/dist/ui/shared/effect-frame.js +26 -7
- package/dist/ui/shared/effect-geometry.js +24 -9
- package/dist/ui/shared/effect-owner-source.js +8 -1
- package/dist/ui/shared/effect-policy.js +21 -7
- package/dist/ui/shared/effect-profiler.js +31 -9
- package/dist/ui/shared/effect-registry.js +97 -50
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +62 -10
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +54 -31
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/identity-card.js +100 -11
- package/dist/ui/shared/identity-color.js +14 -10
- package/dist/ui/shared/instance-nav.js +179 -23
- package/dist/ui/shared/owner-css.js +30 -1
- package/dist/ui/shared/panel-view.js +24 -4
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +20 -7
- package/dist/ui/shared/ui-toast.js +16 -3
- package/dist/ui/shared/usage-dialog.js +34 -3
- package/dist/ui/shared/view-strings.js +72 -0
- package/dist/ui/shared/web-authorize.js +198 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/ui/web-terminal/terminal.html +1 -1
- package/dist/update-launch.d.ts +9 -0
- package/dist/update-launch.js +38 -12
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/format-rich.js +6 -3
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +2 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/i18n.d.ts +13 -1
- package/dist/usage/i18n.js +29 -0
- package/dist/usage/i18n.js.map +1 -1
- package/dist/usage/providers.d.ts +81 -2
- package/dist/usage/providers.js +240 -38
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +5 -1
- package/dist/usage/usage-api.js +184 -58
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/user-systemd.d.ts +24 -0
- package/dist/user-systemd.js +75 -0
- package/dist/user-systemd.js.map +1 -0
- package/dist/view-api.d.ts +39 -0
- package/dist/view-api.js +69 -6
- package/dist/view-api.js.map +1 -1
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +161 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +98 -14
- package/dist/web-api.js +1027 -83
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-policy.d.ts +4 -6
- package/dist/web-appearance-policy.js +5 -12
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +3 -0
- package/dist/web-appearance-tools.js +22 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +27 -2
- package/dist/web-appearance-view.js +129 -14
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +17 -3
- package/dist/web-appearance.js +85 -8
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +2 -2
- package/dist/web-asset-parts.js +3 -3
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +3 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +130 -0
- package/dist/web-backend-login.js +225 -0
- package/dist/web-backend-login.js.map +1 -0
- package/dist/web-card.d.ts +14 -0
- package/dist/web-card.js +40 -6
- package/dist/web-card.js.map +1 -1
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +83 -0
- package/dist/web-chat-history.js +137 -5
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +6 -1
- package/dist/web-commands.js +1 -1
- package/dist/web-commands.js.map +1 -1
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.js +46 -1
- package/dist/web-effect-bundle.js.map +1 -1
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +4 -4
- package/dist/web-effect-service.d.ts +11 -4
- package/dist/web-effect-service.js +25 -4
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.d.ts +1 -0
- package/dist/web-effect-tools.js +4 -4
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.d.ts +2 -2
- package/dist/web-effect-trials.js +3 -3
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect-worker.d.ts +1 -1
- package/dist/web-effect-worker.js +2 -2
- package/dist/web-effect-worker.js.map +1 -1
- package/dist/web-effect.d.ts +1 -1
- package/dist/web-guest-access.d.ts +126 -0
- package/dist/web-guest-access.js +238 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-host-guard.d.ts +11 -0
- package/dist/web-host-guard.js +11 -0
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-identity-color.d.ts +1 -1
- package/dist/web-identity-color.js +2 -2
- package/dist/web-identity-color.js.map +1 -1
- package/dist/web-identity-palette.d.ts +1 -1
- package/dist/web-identity-palette.js +2 -2
- package/dist/web-identity-palette.js.map +1 -1
- package/dist/web-image-meta.d.ts +13 -0
- package/dist/web-image-meta.js +62 -0
- package/dist/web-image-meta.js.map +1 -1
- package/dist/web-libavoid.d.ts +14 -0
- package/dist/web-libavoid.js +46 -0
- package/dist/web-libavoid.js.map +1 -0
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +1 -1
- package/dist/web-mod-agent.js +7 -5
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +9 -1
- package/dist/web-mod-bundle.js +62 -3
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-export.d.ts +9 -0
- package/dist/web-mod-export.js +21 -0
- package/dist/web-mod-export.js.map +1 -1
- package/dist/web-mod-store.d.ts +7 -1
- package/dist/web-mod-store.js +19 -6
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +44 -1
- package/dist/web-owner-css-store.js +70 -9
- package/dist/web-owner-css-store.js.map +1 -1
- package/dist/web-prefs.d.ts +14 -0
- package/dist/web-prefs.js +37 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -2
- package/dist/web-preview.js +4 -2
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +41 -0
- package/dist/web-returning-device.js +57 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +70 -10
- package/dist/web-session.js +137 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +49 -2
- package/dist/web-settings-patch.js +145 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +94 -0
- package/dist/web-settings-tools.js +301 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-terminal-http.js +15 -2
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-theme-store.d.ts +39 -0
- package/dist/web-theme-store.js +122 -11
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-theme.d.ts +31 -0
- package/dist/web-theme.js +99 -2
- package/dist/web-theme.js.map +1 -1
- package/dist/web-upload.d.ts +13 -0
- package/dist/web-upload.js +39 -3
- package/dist/web-upload.js.map +1 -1
- package/dist/windows-shortcuts.d.ts +78 -0
- package/dist/windows-shortcuts.js +203 -0
- package/dist/windows-shortcuts.js.map +1 -0
- package/dist/workflow-templates/default.md +4 -1
- package/dist/yaml-shared-nodes.d.ts +25 -0
- package/dist/yaml-shared-nodes.js +185 -0
- package/dist/yaml-shared-nodes.js.map +1 -0
- package/launcher/agend-install.cjs +553 -0
- package/launcher/install-admission.cjs +4 -39
- package/launcher/postinstall.cjs +14 -12
- package/package.json +4 -2
- package/scripts/preinstall-guard.cjs +2 -5
- package/templates/windows-shortcuts.ps1 +181 -0
- package/launcher/old-updater-note.cjs +0 -48
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Public (/assets/): an anonymous View reader gets the same list, fed by View's roster read instead of status frames.
|
|
10
10
|
import { html, useLayoutEffect, useRef, useState } from "./app-html.js";
|
|
11
11
|
import { t } from "./app-i18n.js";
|
|
12
|
-
import { appStore, createStore, useStore } from "./app-store.js";
|
|
12
|
+
import { appStore, createStore, guestPage, useStore } from "./app-store.js";
|
|
13
13
|
import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
|
|
14
14
|
import { Icon } from "./ui-icons.js";
|
|
15
15
|
import { Skeleton } from "./ui-states.js";
|
|
@@ -30,12 +30,52 @@ const stored = (k) => { try { return localStorage.getItem(k); } catch { return n
|
|
|
30
30
|
const nameOf = (it) => (it && (it.name != null ? it.name : it.instance_name)) || "";
|
|
31
31
|
|
|
32
32
|
// ── Groups, labels, the filter ──
|
|
33
|
+
/** A team's group key: no tag can be it (a group name is never a control character), so a tag named like a team stays apart. */
|
|
34
|
+
export const TEAM_GROUP = "\u0001team:";
|
|
35
|
+
/**
|
|
36
|
+
* #2172: an agent in a team (fleet.yaml `teams.<name>.members`; `teams` on a status or roster row, in fleet.yaml's
|
|
37
|
+
* order) sits under its first team, whatever its tags say; one in no team under its first tag; a ClassicBot room under
|
|
38
|
+
* "Classic"; the rest under "Other".
|
|
39
|
+
*/
|
|
33
40
|
export function groupOf(it) {
|
|
34
41
|
const tags = Array.isArray(it.tags) ? it.tags : [];
|
|
35
42
|
if (tags.includes("classic")) return "Classic";
|
|
43
|
+
const teams = Array.isArray(it.teams) ? it.teams.filter((x) => typeof x === "string" && x) : [];
|
|
44
|
+
if (teams.length) return TEAM_GROUP + teams[0];
|
|
36
45
|
if (tags.length) return tags[0];
|
|
37
46
|
return "Other";
|
|
38
47
|
}
|
|
48
|
+
/** "team" for a team's group, "tag" for a tag's; null for Classic, Other and Favorites. */
|
|
49
|
+
export function groupKind(g) {
|
|
50
|
+
if (typeof g !== "string") return null;
|
|
51
|
+
if (g.startsWith(TEAM_GROUP)) return "team";
|
|
52
|
+
return g === "Other" || g === "Classic" || g.charCodeAt(0) < 32 ? null : "tag";
|
|
53
|
+
}
|
|
54
|
+
/** A group's heading: a team's name; the fallback group ("Other") in the page's language; a tag as it is. */
|
|
55
|
+
export function groupLabel(g) { return g === "Other" ? tn("groupOther") : groupKind(g) === "team" ? g.slice(TEAM_GROUP.length) : g; }
|
|
56
|
+
/** #2172: what a group is, said on its heading — a team's icon, or "#" before a tag — so the grouping can be read. */
|
|
57
|
+
export function GroupMark({ group }) {
|
|
58
|
+
const kind = groupKind(group);
|
|
59
|
+
if (!kind) return null;
|
|
60
|
+
const what = tn(kind === "team" ? "groupTeam" : "groupTag");
|
|
61
|
+
return kind === "team"
|
|
62
|
+
? html`<span class="v-group-kind team" title=${what}><${Icon} name="team" size=${13} /><span class="sr-only">${what}: </span></span>`
|
|
63
|
+
: html`<span class="v-group-kind tag" title=${what} aria-hidden="true">#</span>`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* #2050: the model as a person reads it. The server's resolver writes English for a model it has not resolved yet
|
|
67
|
+
* ("default (detected when it starts)") and for a CLI's own default ("x (default)"); those are shown in the page's language.
|
|
68
|
+
*/
|
|
69
|
+
export function modelText(it, raw = it && it.model) {
|
|
70
|
+
if (!raw || typeof raw !== "string") return raw;
|
|
71
|
+
if (it.model_source === "unresolved") {
|
|
72
|
+
const why = /^default \((.*)\)$/.exec(raw)?.[1];
|
|
73
|
+
const reason = why === "detected when it starts" ? tn("modelWhyStart") : why === "this CLI does not report a default" ? tn("modelWhyCli") : why;
|
|
74
|
+
return reason ? tn("modelDefaultWhy", { reason }) : raw === "default" ? tn("modelDefault") : raw;
|
|
75
|
+
}
|
|
76
|
+
if (it.model_source === "cli-default") { const m = /^(.*) \(default\)$/.exec(raw); if (m) return tn("modelCliDefault", { model: m[1] }); }
|
|
77
|
+
return raw;
|
|
78
|
+
}
|
|
39
79
|
const BACKEND_LABELS = { "claude-code": "Claude Code", "kiro-cli": "Kiro CLI", codex: "Codex", grok: "Grok Build", antigravity: "Antigravity", opencode: "OpenCode" };
|
|
40
80
|
export function backendLabel(backend) { return BACKEND_LABELS[backend] || backend || "Unknown"; }
|
|
41
81
|
export const KNOWN_BACKENDS = new Set(Object.keys(BACKEND_LABELS));
|
|
@@ -93,8 +133,8 @@ export function instanceTooltip(it) {
|
|
|
93
133
|
if (it.display_name && it.display_name !== name) lines.push(it.display_name, `(${name})`);
|
|
94
134
|
else lines.push(name);
|
|
95
135
|
lines.push(line("tooltipBackend", backendLabel(it.backend)));
|
|
96
|
-
const src = (s) => (s === "instance" || s === "classic" ? "
|
|
97
|
-
lines.push(line("tooltipModel", (it
|
|
136
|
+
const src = (s) => (s === "instance" || s === "classic" ? tn("srcConfigured") : s === "fleet-default" ? tn("srcFleetDefault") : "");
|
|
137
|
+
lines.push(line("tooltipModel", (modelText(it) || tn("tooltipUnavailable")) + src(it.model_source)));
|
|
98
138
|
lines.push(line("tooltipStatus", status));
|
|
99
139
|
lines.push(line("tooltipContext", context));
|
|
100
140
|
if (it.effort) lines.push(line("tooltipEffort", it.effort + src(it.effort_source)));
|
|
@@ -108,6 +148,7 @@ const SIDEBAR_ORDER_KEY = "agend_view_sidebar_order"; // View's key since #999
|
|
|
108
148
|
const COLLAPSED_KEY = "agend_instance_collapsed";
|
|
109
149
|
const FILTER_KEY = "agend_instance_filter";
|
|
110
150
|
const FAVORITES_KEY = "agend_favorites"; // #1664
|
|
151
|
+
const SORT_KEY = "agend_nav_sort"; // #2179
|
|
111
152
|
/**
|
|
112
153
|
* A synced sidebar preference, as the JSON its reader parses: the value in force in memory (app-prefs.js — this
|
|
113
154
|
* page's own change, else the host's; a full or blocked storage never brings an old one back), else this browser's copy.
|
|
@@ -138,6 +179,13 @@ function readFilter() {
|
|
|
138
179
|
return { q: f && typeof f.q === "string" ? f.q : "", status: strings(f && f.status).filter((s) => STATUS_FACETS.includes(s)), cli: strings(f && f.cli) };
|
|
139
180
|
} catch { return { q: "", status: [], cli: [] }; }
|
|
140
181
|
}
|
|
182
|
+
/** #2179: how the list sorts — { mode: "custom" (the person's order; the default) | "recent", favs: "fixed" | "recent" }. */
|
|
183
|
+
function readSort() {
|
|
184
|
+
try {
|
|
185
|
+
const s = JSON.parse(inForce("nav.sort", SORT_KEY) || "{}");
|
|
186
|
+
return { mode: s && s.mode === "recent" ? "recent" : "custom", favs: s && s.favs === "recent" ? "recent" : "fixed" };
|
|
187
|
+
} catch { return { mode: "custom", favs: "fixed" }; }
|
|
188
|
+
}
|
|
141
189
|
function readFavorites() {
|
|
142
190
|
try { const a = JSON.parse(inForce("nav.favorites", FAVORITES_KEY) || "[]"); return Array.isArray(a) ? [...new Set(a.filter((x) => typeof x === "string"))] : []; } catch { return []; }
|
|
143
191
|
}
|
|
@@ -151,7 +199,9 @@ export function computeOrder(roster, order) {
|
|
|
151
199
|
if (ia !== ib) return ia - ib;
|
|
152
200
|
if (a === "Other") return 1;
|
|
153
201
|
if (b === "Other") return -1;
|
|
154
|
-
|
|
202
|
+
const ta = groupKind(a) === "team", tb = groupKind(b) === "team";
|
|
203
|
+
if (ta !== tb) return ta ? -1 : 1; // #2172: teams first, then tags
|
|
204
|
+
return groupLabel(a).localeCompare(groupLabel(b));
|
|
155
205
|
});
|
|
156
206
|
const instByGroup = new Map();
|
|
157
207
|
for (const [g, items] of groups) {
|
|
@@ -174,12 +224,18 @@ function moveIn(arr, moving, target, before) {
|
|
|
174
224
|
}
|
|
175
225
|
|
|
176
226
|
/** The list's state for the page's life: this browser's order, folded groups and filter (each also kept in storage). */
|
|
177
|
-
export const navStore = createStore({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites() });
|
|
227
|
+
export const navStore = createStore({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites(), sort: readSort() });
|
|
178
228
|
/** Read them again from this browser's storage — what a page load does (the store above is made that way). */
|
|
179
229
|
export function loadNavPrefs() {
|
|
180
|
-
navStore.set({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites() });
|
|
230
|
+
navStore.set({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites(), sort: readSort() });
|
|
181
231
|
namesStore.set({ primary: readNamesPrimary() });
|
|
182
232
|
}
|
|
233
|
+
/** #2179: change how the list sorts ({ mode?, favs? }): the same on every page and device. */
|
|
234
|
+
export function setNavSort(patch) {
|
|
235
|
+
const sort = { ...navStore.get().sort, ...patch };
|
|
236
|
+
navStore.set({ sort });
|
|
237
|
+
savePref("nav.sort", { mode: sort.mode, favs: sort.favs });
|
|
238
|
+
}
|
|
183
239
|
/** Change the filter ({ q?, status?, cli? }): the same on every page, and kept by this browser. */
|
|
184
240
|
export function setNavFilter(patch) {
|
|
185
241
|
const filter = { ...navStore.get().filter, ...patch };
|
|
@@ -188,7 +244,7 @@ export function setNavFilter(patch) {
|
|
|
188
244
|
}
|
|
189
245
|
function setCollapsed(collapsed) { navStore.set({ collapsed }); savePref("nav.collapsed", [...collapsed]); }
|
|
190
246
|
// Another device rearranged, folded or filtered: this page's list follows (its cache is already updated).
|
|
191
|
-
onPrefsChanged((keys) => { if (keys.has("nav.order") || keys.has("nav.collapsed") || keys.has("nav.filter") || keys.has("nav.favorites")) loadNavPrefs(); });
|
|
247
|
+
onPrefsChanged((keys) => { if (keys.has("nav.order") || keys.has("nav.collapsed") || keys.has("nav.filter") || keys.has("nav.favorites") || keys.has("nav.sort")) loadNavPrefs(); });
|
|
192
248
|
|
|
193
249
|
// ── #1664: favorites — starred instances, pinned at the top in the order they were starred (or moved) ──
|
|
194
250
|
export const MAX_FAVORITES = 50;
|
|
@@ -339,6 +395,71 @@ export function InstanceAvatar({ it, state, cli = false }) {
|
|
|
339
395
|
const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet === "working" ? t("app.statusWorking") : facet === "idle" ? t("app.statusIdle")
|
|
340
396
|
: facet === "crashed" ? t("app.statusCrashed") : t("app.statusStopped"));
|
|
341
397
|
|
|
398
|
+
// ── #2179: when each instance last replied to a person (the status frame's last_reply) ──
|
|
399
|
+
/** A reply this recent (minutes) is highlighted; the highlight fades to nothing over this time. */
|
|
400
|
+
export const REPLY_FRESH_MIN = 10;
|
|
401
|
+
/** Minutes since the instance last replied to a person, or null (none kept, or no time on this row). */
|
|
402
|
+
export function replyAge(it, now = Date.now()) {
|
|
403
|
+
const at = it && typeof it.last_reply === "string" ? Date.parse(it.last_reply) : NaN;
|
|
404
|
+
return Number.isFinite(at) ? Math.max(0, (now - at) / 60000) : null;
|
|
405
|
+
}
|
|
406
|
+
/** "just now", "2 min ago", "3 h ago", "yesterday", "4 d ago" — in the page's language. */
|
|
407
|
+
export function replyText(mins) {
|
|
408
|
+
if (mins < 1) return tn("replyJustNow");
|
|
409
|
+
if (mins < 60) return tn("replyMinutes", { n: Math.floor(mins) });
|
|
410
|
+
if (mins < 1440) return tn("replyHours", { n: Math.floor(mins / 60) });
|
|
411
|
+
if (mins < 2880) return tn("replyYesterday");
|
|
412
|
+
return tn("replyDays", { n: Math.floor(mins / 1440) });
|
|
413
|
+
}
|
|
414
|
+
/** How strong the highlight is: 1 just after a reply, down to 0 at REPLY_FRESH_MIN minutes. */
|
|
415
|
+
export function replyFade(mins) { return mins == null || mins >= REPLY_FRESH_MIN ? 0 : 1 - mins / REPLY_FRESH_MIN; }
|
|
416
|
+
/** The highlight's strength on the row, through the CSSOM (style-src has no 'unsafe-inline'). */
|
|
417
|
+
function paintReply(el, fade) {
|
|
418
|
+
if (!el) return;
|
|
419
|
+
if (fade > 0) el.style.setProperty("--reply-a", fade.toFixed(3)); else el.style.removeProperty("--reply-a");
|
|
420
|
+
}
|
|
421
|
+
/** Most recent reply first; rows with none keep their order after them (a stable sort). */
|
|
422
|
+
function byRecentReply(age) {
|
|
423
|
+
return (a, b) => {
|
|
424
|
+
const x = age(a), y = age(b);
|
|
425
|
+
if (x == null || y == null) return x == null ? (y == null ? 0 : 1) : -1;
|
|
426
|
+
return x - y;
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/** The group that holds every non-favorite row in "recent" mode: no tag or team can be it, and it is never saved. */
|
|
430
|
+
const RECENT_GROUP = "\u0000recent";
|
|
431
|
+
|
|
432
|
+
/** #2179: the sort button and its menu — how the list sorts, said on the button. */
|
|
433
|
+
function SortMenu({ sort }) {
|
|
434
|
+
const [open, setOpen] = useState(false);
|
|
435
|
+
const box = useRef(null);
|
|
436
|
+
useLayoutEffect(() => {
|
|
437
|
+
if (!open) return undefined;
|
|
438
|
+
const doc = box.current && box.current.ownerDocument;
|
|
439
|
+
if (!doc) return undefined;
|
|
440
|
+
const away = (e) => { if (box.current && !box.current.contains(e.target)) setOpen(false); };
|
|
441
|
+
const esc = (e) => { if (e.key === "Escape") { e.preventDefault(); setOpen(false); } };
|
|
442
|
+
doc.addEventListener("pointerdown", away, true);
|
|
443
|
+
doc.addEventListener("keydown", esc, true);
|
|
444
|
+
return () => { doc.removeEventListener("pointerdown", away, true); doc.removeEventListener("keydown", esc, true); };
|
|
445
|
+
}, [open]);
|
|
446
|
+
const recent = sort.mode === "recent";
|
|
447
|
+
const radio = (group, value, label, hint) => html`<label class="nav-sort-opt"><input type="radio" name=${`nav-sort-${group}`} value=${value}
|
|
448
|
+
checked=${sort[group] === value} onChange=${() => setNavSort({ [group]: value })} /><span>${label}${hint ? html`<span class="nav-sort-hint">${hint}</span>` : null}</span></label>`;
|
|
449
|
+
return html`<div class="nav-sort" ref=${box}>
|
|
450
|
+
<button type="button" class=${`chip-btn nav-sort-btn${recent ? " on" : ""}`} aria-haspopup="dialog" aria-expanded=${open ? "true" : "false"}
|
|
451
|
+
title=${tn("sortTitle")} onClick=${() => setOpen(!open)}><${Icon} name="sort" size=${12} /><span>${tn(recent ? "sortRecent" : "sortCustom")}</span></button>
|
|
452
|
+
${open ? html`<div class="nav-sort-menu" role="dialog" aria-label=${tn("sortTitle")}>
|
|
453
|
+
<fieldset><legend>${tn("sortTitle")}</legend>
|
|
454
|
+
${radio("mode", "custom", tn("sortCustom"), tn("sortCustomHint"))}
|
|
455
|
+
${radio("mode", "recent", tn("sortRecent"), tn("sortRecentHint"))}</fieldset>
|
|
456
|
+
<fieldset><legend>${tn("sortFavs")}</legend>
|
|
457
|
+
${radio("favs", "fixed", tn("sortFavsFixed"), "")}
|
|
458
|
+
${radio("favs", "recent", tn("sortFavsRecent"), "")}</fieldset>
|
|
459
|
+
</div>` : null}
|
|
460
|
+
</div>`;
|
|
461
|
+
}
|
|
462
|
+
|
|
342
463
|
/**
|
|
343
464
|
* The sidebar's instance list. `route`: the page's (which instance is open, and which view a row opens). `items`: the
|
|
344
465
|
* rows (status frames' instances, or View's roster for an anonymous reader); `loaded` false shows a skeleton.
|
|
@@ -352,6 +473,9 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
352
473
|
const filterRef = useRef(null);
|
|
353
474
|
const list = useRef(null);
|
|
354
475
|
const drag = useRef(null);
|
|
476
|
+
// #2179: the reply times and their highlight move on by themselves — the list is drawn again every 30 s.
|
|
477
|
+
const [, setTick] = useState(0);
|
|
478
|
+
useLayoutEffect(() => { const id = setInterval(() => setTick((n) => n + 1), 30000); return () => clearInterval(id); }, []);
|
|
355
479
|
// #1700: which facet groups are open — this device's (screen layout, not a synced preference), read when the list mounts.
|
|
356
480
|
const [facetOpen, setFacetOpen] = useState(readFacetsOpen);
|
|
357
481
|
const toggleFacet = (key) => setFacetOpen((cur) => { const next = { ...cur, [key]: !cur[key] }; writeFacetsOpen(next); return next; });
|
|
@@ -375,10 +499,30 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
375
499
|
const favs = nav.favorites.filter((n) => known.has(n));
|
|
376
500
|
const favSet = new Set(favs);
|
|
377
501
|
const canStar = !viewOnly && prefsWritable();
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
502
|
+
// #1920: a guest's list is to pick from — no reordering and no filters (one this device kept would hide rows with no
|
|
503
|
+
// way to clear it).
|
|
504
|
+
const guest = guestPage();
|
|
505
|
+
// #2179: when each row last replied (a guest's list has no times). "recent" mode (never a guest's): the favorites keep
|
|
506
|
+
// their order or are sorted too, and every other row is one list, the last reply first. "custom" (the default) is the
|
|
507
|
+
// person's own order — a reply never moves a row.
|
|
508
|
+
const now = Date.now();
|
|
509
|
+
// The anonymous View reader (viewOnly) has no reply times (/api/profiles carries none) and no way to change the sort:
|
|
510
|
+
// its list is always Custom, whatever this browser kept from a signed-in page (#2181 review).
|
|
511
|
+
const ageOf = (n) => (guest || viewOnly ? null : replyAge(byName.get(n), now));
|
|
512
|
+
const recent = !guest && !viewOnly && nav.sort.mode === "recent";
|
|
513
|
+
const favsSorted = recent && nav.sort.favs === "recent";
|
|
514
|
+
let shownGroups, shownInsts;
|
|
515
|
+
if (recent) {
|
|
516
|
+
const sortRecent = byRecentReply(ageOf);
|
|
517
|
+
const rest = groupNames.flatMap((g) => instByGroup.get(g) || []).filter((n) => !favSet.has(n)).sort(sortRecent);
|
|
518
|
+
shownGroups = [...(favs.length ? [FAV_GROUP] : []), ...(rest.length ? [RECENT_GROUP] : [])];
|
|
519
|
+
shownInsts = new Map([[FAV_GROUP, favsSorted ? [...favs].sort(sortRecent) : favs], [RECENT_GROUP, rest]]);
|
|
520
|
+
} else {
|
|
521
|
+
shownGroups = favs.length ? [FAV_GROUP, ...groupNames] : groupNames;
|
|
522
|
+
shownInsts = new Map([...instByGroup].map(([g, names]) => [g, names.filter((n) => !favSet.has(n))]));
|
|
523
|
+
if (favs.length) shownInsts.set(FAV_GROUP, favs);
|
|
524
|
+
}
|
|
525
|
+
const f = guest ? { q: "", status: [], cli: [] } : nav.filter;
|
|
382
526
|
const shown = filterSidebar(shownGroups, shownInsts, byName, f.q, backendLabel, { status: f.status, cli: f.cli, statusOf: facetOf });
|
|
383
527
|
// #1936: while a filter is on, the instance you have open stays on top, pinned and marked "Current", whether or not
|
|
384
528
|
// it matches; a match is not repeated below. "N / M" still counts the filter's matches (the pinned row counts only
|
|
@@ -392,7 +536,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
392
536
|
const flip = (key, value) => { const cur = new Set(navStore.get().filter[key]); if (cur.has(value)) cur.delete(value); else cur.add(value); setNavFilter({ [key]: [...cur] }); };
|
|
393
537
|
// Drag to reorder: only the full list (a filtered subset would be ambiguous), groups among groups, instances within
|
|
394
538
|
// their group.
|
|
395
|
-
const dragProps = (item) => shown.active ? {} : {
|
|
539
|
+
const dragProps = (item) => shown.active || guest || recent ? {} : { // #2179: not while sorted by reply
|
|
396
540
|
draggable: true,
|
|
397
541
|
onDragStart: (e) => { drag.current = item; e.currentTarget.classList.add("dragging"); try { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", item.name); } catch { /* Firefox needs data */ } },
|
|
398
542
|
onDragEnd: (e) => { e.currentTarget.classList.remove("dragging"); drag.current = null; },
|
|
@@ -447,18 +591,24 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
447
591
|
// #1958: its identity colour — the ring, a left bar and/or the name, as resolved; painted through the CSSOM.
|
|
448
592
|
const id = identityOf(it);
|
|
449
593
|
const full = !starred && favs.length >= MAX_FAVORITES;
|
|
450
|
-
|
|
594
|
+
// #2179 (B5, the user's pick): the % (and the ★ beside the row) on the name's line; the time of the last reply at the
|
|
595
|
+
// end of the second line, highlighted for REPLY_FRESH_MIN minutes, fading by itself (a reply under a minute pulses once).
|
|
596
|
+
const mins = ageOf(name), fade = replyFade(mins);
|
|
597
|
+
const two = !!secondary || mins != null;
|
|
598
|
+
return html`<li key=${name} class=${`v-li${two ? " two-line" : ""}`}><a class=${`inst v-inst${on ? " active" : ""}${fade > 0 ? " replied" : ""}${mins != null && mins < 1 ? " just" : ""} ${identityClasses(id)}`}
|
|
599
|
+
ref=${(el) => { paintIdentity(el, id); paintReply(el, fade); }} href=${rowPath(name)} title=${instanceTooltip(it)}
|
|
451
600
|
aria-current=${on ? "page" : undefined} onClick=${onPick} ...${fav ? {} : dragProps({ type: "instance", name, group: g })}>
|
|
452
601
|
<${InstanceAvatar} it=${it} state=${dotState(it, own(exec, name) ? exec[name] : null, waiting)} cli=${true} />
|
|
453
|
-
<span class="inst-text"><span class="inst-name" title=${label}>${label}</span>
|
|
454
|
-
|
|
602
|
+
<span class="inst-text"><span class="inst-line"><span class="inst-name" title=${label}>${label}</span>
|
|
603
|
+
<span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span></span>
|
|
604
|
+
${two ? html`<span class="inst-line inst-line2"><span class="inst-alt" title=${secondary || undefined}>${secondary || ""}</span>
|
|
605
|
+
${mins != null ? html`<span class=${`inst-when${fade > 0 ? " fresh" : ""}`} title=${tn("replyTitle", { time: new Date(Date.parse(it.last_reply)).toLocaleString() })}>${replyText(mins)}</span>` : null}</span>` : null}
|
|
455
606
|
${facet === "needs" ? html`<span class="inst-sub"><span class="badge-await" title=${waiting || t("app.approxNote")}>${t("app.needsYou")}</span></span>` : null}
|
|
456
607
|
<span class="sr-only">${tn("tooltipBackend", { value: backendLabel(it.backend) })}</span></span>
|
|
457
|
-
<span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span>
|
|
458
608
|
</a>${fav && editing && canStar ? html`<button type="button" class="icon-btn inst-move" aria-label=${tn("favUp", { name: label })}
|
|
459
|
-
title=${shown.active ? tn("favFiltered") : tn("favUp", { name: label })} disabled=${shown.active || i === 0} onClick=${() => { if (!shown.active) moveFavorite(name, -1, known); }}><${Icon} name="up" size=${14} /></button><button type="button" class="icon-btn inst-move"
|
|
460
|
-
aria-label=${tn("favDown", { name: label })} title=${shown.active ? tn("favFiltered") : tn("favDown", { name: label })} disabled=${shown.active || i === count - 1}
|
|
461
|
-
onClick=${() => { if (!shown.active) moveFavorite(name, 1, known); }}><${Icon} name="down" size=${14} /></button>` : null}${canStar
|
|
609
|
+
title=${shown.active ? tn("favFiltered") : favsSorted ? tn("favSorted") : tn("favUp", { name: label })} disabled=${shown.active || favsSorted || i === 0} onClick=${() => { if (!shown.active && !favsSorted) moveFavorite(name, -1, known); }}><${Icon} name="up" size=${14} /></button><button type="button" class="icon-btn inst-move"
|
|
610
|
+
aria-label=${tn("favDown", { name: label })} title=${shown.active ? tn("favFiltered") : favsSorted ? tn("favSorted") : tn("favDown", { name: label })} disabled=${shown.active || favsSorted || i === count - 1}
|
|
611
|
+
onClick=${() => { if (!shown.active && !favsSorted) moveFavorite(name, 1, known); }}><${Icon} name="down" size=${14} /></button>` : null}${canStar
|
|
462
612
|
? html`<button type="button" class=${`icon-btn inst-star${starred ? " on" : ""}`} aria-pressed=${starred ? "true" : "false"} disabled=${full}
|
|
463
613
|
aria-label=${tn(starred ? "favRemove" : "favAdd", { name: label })} title=${full ? tn("favFull", { max: MAX_FAVORITES }) : tn(starred ? "favRemove" : "favAdd", { name: label })}
|
|
464
614
|
onClick=${() => toggleFavorite(name, known)}><${Icon} name="star" size=${14} /></button>` : null}</li>`;
|
|
@@ -467,6 +617,8 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
467
617
|
return html`<div class=${`view-roster inst-nav${editing && canStar ? " fav-editing" : ""}`}>
|
|
468
618
|
<div data-trust="navigation" class="side-section" id="instanceList" ref=${list}>
|
|
469
619
|
<div class="inst-head"><h2 class="side-label">${tn("instancesLabel")}</h2>
|
|
620
|
+
${canStar && items.length && !guest ? html`<${SortMenu} sort=${nav.sort} />`
|
|
621
|
+
: recent ? html`<span class="chip-btn nav-sort-btn on nav-sort-static"><${Icon} name="sort" size=${12} /><span>${tn("sortRecent")}</span></span>` : null}
|
|
470
622
|
${canStar && items.length ? html`<button type="button" class="chip-btn fav-edit" aria-pressed=${editing ? "true" : "false"} onClick=${() => setEditing(!editing)}>
|
|
471
623
|
${editing ? tn("favDone") : html`<${Icon} name="star" size=${12} />${tn("favEdit")}`}</button>` : null}</div>
|
|
472
624
|
${!loaded ? html`<${Skeleton} lines=${4} />` : !items.length ? html`<p class="side-empty">${t("app.noInstances")}</p>` : null}
|
|
@@ -474,6 +626,10 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
474
626
|
<div class="v-group-head pinned-head"><span class="grow">${tn("pinnedCurrent")}</span></div>
|
|
475
627
|
<ul class="inst-list">${row(pinned, PINNED_GROUP, 0, 1)}</ul></div>` : null}
|
|
476
628
|
${listed.map(({ group: g, names }) => {
|
|
629
|
+
// #2179: in "recent" mode every other row is one list — open, not dragged, not saved as a group.
|
|
630
|
+
if (g === RECENT_GROUP) return html`<div key=${g} class="v-group v-recent" role="group" aria-label=${tn("recentOthers")}>
|
|
631
|
+
<div class="v-group-head recent-head"><${Icon} name="clock" size=${14} /><span class="grow">${tn("recentOthers")}</span><span class="count">${names.length}</span></div>
|
|
632
|
+
<ul class="inst-list">${names.map((name, i) => row(name, g, i, names.length))}</ul></div>`;
|
|
477
633
|
// #1664: Favorites — always open, not dragged (↑ ↓ in Edit), named in the page's language.
|
|
478
634
|
if (g === FAV_GROUP) return html`<div key=${g} class="v-group v-fav" role="group" aria-label=${tn("favorites")}>
|
|
479
635
|
<div class="v-group-head fav-head"><${Icon} name="star" size=${14} cls="fav-mark" /><span class="grow">${tn("favorites")}</span><span class="count">${names.length}</span></div>
|
|
@@ -481,14 +637,14 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
481
637
|
const folded = nav.collapsed.has(g);
|
|
482
638
|
return html`<div key=${g} class="v-group">
|
|
483
639
|
<button type="button" class="v-group-head" aria-expanded=${folded ? "false" : "true"} onClick=${() => toggle(g)}
|
|
484
|
-
title=${tn(folded ? "expandGroup" : "collapseGroup", { group: g })} ...${dragProps({ type: "group", name: g })}>
|
|
485
|
-
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><span class="grow">${g}</span><span class="count">${names.length}</span></button>
|
|
640
|
+
title=${tn(folded ? "expandGroup" : "collapseGroup", { group: groupLabel(g) })} ...${dragProps({ type: "group", name: g })}>
|
|
641
|
+
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><${GroupMark} group=${g} /><span class="grow">${groupLabel(g)}</span><span class="count">${names.length}</span></button>
|
|
486
642
|
${folded ? null : html`<ul class="inst-list">${names.map((name, i) => row(name, g, i, names.length))}</ul>`}
|
|
487
643
|
</div>`;
|
|
488
644
|
})}
|
|
489
645
|
${loaded && shown.active && !shown.groups.length ? html`<p class="side-empty">${tn("filterNone")}</p>` : null}
|
|
490
646
|
</div>
|
|
491
|
-
|
|
647
|
+
${guest ? null : html`<div class="v-filter">
|
|
492
648
|
<label class="v-filter-box"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("filterPh")}</span>
|
|
493
649
|
<input ref=${filterRef} id="filterInput" type="text" autocomplete="off" spellcheck=${false} placeholder=${tn("filterPh")} value=${f.q}
|
|
494
650
|
onInput=${(e) => setNavFilter({ q: e.target.value })}
|
|
@@ -504,7 +660,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
504
660
|
</div>
|
|
505
661
|
<p class="note nav-shown" aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? ` ${tn("filterShown")}` : ""}</p>
|
|
506
662
|
</div>
|
|
507
|
-
</div
|
|
663
|
+
</div>`}
|
|
508
664
|
</div>`;
|
|
509
665
|
}
|
|
510
666
|
|
|
@@ -80,6 +80,7 @@ function sync() {
|
|
|
80
80
|
let valid = false;
|
|
81
81
|
try { valid = link.disabled !== true && !!link.sheet && link.sheet.ownerNode === link && link.sheet.disabled !== true && link.sheet.href === new URL(href, location.href).href; } catch { /* unknown stylesheet */ }
|
|
82
82
|
linkState = valid ? "on" : "unknown"; life(linkState, valid ? "loaded" : "load_unverified");
|
|
83
|
+
if (valid) shownHere(sha); // #2198: an agent's trial is on this screen now
|
|
83
84
|
});
|
|
84
85
|
link.addEventListener("error", () => { if (!current()) return; linkState = "unknown"; life("unknown", "load_failed"); });
|
|
85
86
|
life("loading", "loading");
|
|
@@ -189,12 +190,40 @@ async function reread() {
|
|
|
189
190
|
if (body && mine === gen) reconcile(body); // nothing newer happened since this was asked
|
|
190
191
|
} catch { /* the next change tries again */ }
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* #2198: an agent's trial (General saved it) waits on the host until a person's page shows it. This page shows it once
|
|
195
|
+
* its stylesheet has loaded on screen, and says so: the host starts the keep window then (the dead-man switch, from when
|
|
196
|
+
* a person can see the Keep button). If the host cannot be told, this page reverts it itself at KEEP_SECONDS, as it does
|
|
197
|
+
* for a version it saved.
|
|
198
|
+
*/
|
|
199
|
+
let waitingShown = null; // { id, sha } of an agent's trial this page is showing and has not reported yet
|
|
200
|
+
function shownHere(sha) {
|
|
201
|
+
const w = waitingShown;
|
|
202
|
+
if (!w || w.sha !== sha) return;
|
|
203
|
+
waitingShown = null;
|
|
204
|
+
const trial = ownerCssTrial.get();
|
|
205
|
+
if (!trial || trial.id !== w.id) return;
|
|
206
|
+
post("/ui/owner-css/shown", { trial: w.id }).then((r) => {
|
|
207
|
+
if (ownerCssTrial.get() !== trial) return;
|
|
208
|
+
const ms = r.ok && r.body && Number.isFinite(r.body.remaining_ms) ? r.body.remaining_ms : null;
|
|
209
|
+
if (ms === null) { startOwnerCssTrial(w.id, w.sha, trial.previous, KEEP_SECONDS, true); return; }
|
|
210
|
+
startOwnerCssTrial(w.id, w.sha, trial.previous, ms / 1000 + 1, false);
|
|
211
|
+
}, () => { if (ownerCssTrial.get() === trial) startOwnerCssTrial(w.id, w.sha, trial.previous, KEEP_SECONDS, true); });
|
|
212
|
+
}
|
|
192
213
|
/** Match the host: the version in force, and whether one is on trial (another device's included) — or none any more. */
|
|
193
214
|
function reconcile(state) {
|
|
194
215
|
const pending = state.pending && state.pending.sha === state.active && typeof state.pending.id === "string" ? state.pending : null;
|
|
195
216
|
const trial = ownerCssTrial.get();
|
|
196
|
-
if (!pending) { if (trial) dropTrial(); showActive(state.active, state.effectState); return; }
|
|
217
|
+
if (!pending) { waitingShown = null; if (trial) dropTrial(); showActive(state.active, state.effectState); return; }
|
|
197
218
|
if (trial && trial.id === pending.id) return; // already asking about this one
|
|
219
|
+
if (pending.waiting === true) {
|
|
220
|
+
// Shown here: its stylesheet goes in, and once it has loaded this page reports it (shownHere). Until then the bar
|
|
221
|
+
// waits on the host's word, as for another device's trial.
|
|
222
|
+
waitingShown = { id: pending.id, sha: pending.sha };
|
|
223
|
+
startOwnerCssTrial(pending.id, pending.sha, pending.previous, HOST_TRIAL_SECONDS, false);
|
|
224
|
+
if (linkState === "on" && ownedLink && ownedLink.getAttribute("href") === `/ui/owner-css.css?v=${encodeURIComponent(pending.sha)}`) shownHere(pending.sha);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
198
227
|
// The host's own remaining time and a second (never this device's clock): it has settled the trial by then.
|
|
199
228
|
const remaining = typeof pending.remaining_ms === "number" && Number.isFinite(pending.remaining_ms) ? pending.remaining_ms : 0;
|
|
200
229
|
startOwnerCssTrial(pending.id, pending.sha, pending.previous, remaining / 1000 + 1, false);
|
|
@@ -16,7 +16,7 @@ import { HeaderTools, VIEW_SCALE, textSizeStore } from "./header-tools.js";
|
|
|
16
16
|
import { readStream } from "./read-stream.js";
|
|
17
17
|
// The usage dialog's metric row lives with the dialog now (usage-dialog.js); re-exported for what imported it here.
|
|
18
18
|
export { UsageMetric } from "./usage-dialog.js";
|
|
19
|
-
import { backendLabel, STATUS_KEYS } from "./instance-nav.js";
|
|
19
|
+
import { backendLabel, modelText, STATUS_KEYS } from "./instance-nav.js";
|
|
20
20
|
import { viewStore } from "./view-roster-store.js";
|
|
21
21
|
|
|
22
22
|
/** #1580: the app's stream says the fleet is not answering (a restart): the View's polls skip their turn until it is. */
|
|
@@ -27,7 +27,7 @@ import { Dialog } from "./ui-dialog.js";
|
|
|
27
27
|
import { Empty, ErrorState, Skeleton } from "./ui-states.js";
|
|
28
28
|
import { Icon } from "./ui-icons.js";
|
|
29
29
|
import "./view-strings.js";
|
|
30
|
-
import { checkCard, fitPicture, IdentityCard, useCard } from "./identity-card.js";
|
|
30
|
+
import { BannerFocusPreview, checkCard, fitPicture, IdentityCard, useBannerPreview, useCard } from "./identity-card.js";
|
|
31
31
|
import { confirmDialog } from "./ui-confirm.js";
|
|
32
32
|
import { openColorDialog } from "./identity-color.js";
|
|
33
33
|
|
|
@@ -305,7 +305,7 @@ function Card({ it, viewOnly, onEdit }) {
|
|
|
305
305
|
<div class="v-avatar">${it.has_avatar ? html`<img ref=${avatar} alt="" />` : html`<span class="v-initial" aria-hidden="true">${initial}</span>`}</div>
|
|
306
306
|
<div class="v-card-body">
|
|
307
307
|
<div class="v-card-line"><strong>${it.display_name || it.instance_name}</strong>${it.role ? html`<span class="v-role">${it.role}</span>` : null}</div>
|
|
308
|
-
<div class="v-card-meta">${it.instance_name} · ${backendLabel(it.backend)}${it.model ? ` · ${it
|
|
308
|
+
<div class="v-card-meta">${it.instance_name} · ${backendLabel(it.backend)}${it.model ? ` · ${modelText(it)}` : ""}${ctx}</div>
|
|
309
309
|
${open && it.description ? html`<p class="v-card-desc">${it.description}</p>` : null}
|
|
310
310
|
</div>
|
|
311
311
|
<button type="button" class="icon-btn" onClick=${toggle} aria-expanded=${open ? "true" : "false"} aria-label=${tn(open ? "collapseProfile" : "expandProfile")} title=${tn(open ? "collapseProfile" : "expandProfile")}>
|
|
@@ -332,6 +332,11 @@ function EditProfile({ it, onClose }) {
|
|
|
332
332
|
const [removeCard, setRemoveCard] = useState({ portrait: false, banner: false });
|
|
333
333
|
const [clearCard, setClearCard] = useState(false);
|
|
334
334
|
const pics = { portrait: useRef(null), banner: useRef(null) };
|
|
335
|
+
// #2086: the banner's focus — this person's (null: follow the agent's, else the centre), and the picture to preview:
|
|
336
|
+
// a newly chosen file, else the banner drawn now.
|
|
337
|
+
// `agent`: the agent's own focus (what "Follow the agent's" shows; else the centre).
|
|
338
|
+
const [focus, setFocus] = useState({ value: 50, mine: null, start: null, agent: null });
|
|
339
|
+
const [bannerPreview, preview] = useBannerPreview();
|
|
335
340
|
useEffect(() => {
|
|
336
341
|
let live = true;
|
|
337
342
|
// The card's fields stay disabled until this read is in (#1889 review: a draft typed meanwhile was overwritten);
|
|
@@ -343,6 +348,11 @@ function EditProfile({ it, onClose }) {
|
|
|
343
348
|
const resolved = checkCard(it.instance_name, body);
|
|
344
349
|
const mine = (k) => (resolved && resolved[k] && resolved[k].by === "human" ? resolved[k] : null);
|
|
345
350
|
setCard({ mine: { tagline: mine("tagline"), bio: mine("bio"), portrait: mine("portrait"), banner: mine("banner") }, limits: body.limits || null });
|
|
351
|
+
const bf = resolved && resolved.banner_focus;
|
|
352
|
+
const agent = Number.isInteger(body.banner_focus_agent) && body.banner_focus_agent >= 0 && body.banner_focus_agent <= 100 ? body.banner_focus_agent : null;
|
|
353
|
+
const mineFocus = bf && bf.by === "human" ? bf.value : null;
|
|
354
|
+
setFocus({ value: mineFocus ?? agent ?? 50, mine: mineFocus, start: mineFocus, agent });
|
|
355
|
+
if (resolved && resolved.banner) preview.fromSrc(resolved.banner.src);
|
|
346
356
|
setCardText({ tagline: mine("tagline") ? mine("tagline").text : "", bio: mine("bio") ? mine("bio").text : "" });
|
|
347
357
|
}).catch(() => { if (live && lease.current()) setCard(none); });
|
|
348
358
|
return () => { live = false; };
|
|
@@ -360,6 +370,7 @@ function EditProfile({ it, onClose }) {
|
|
|
360
370
|
const clearing = clearCard;
|
|
361
371
|
const before = card && card.mine ? { tagline: card.mine.tagline ? card.mine.tagline.text : "", bio: card.mine.bio ? card.mine.bio.text : "" } : { tagline: "", bio: "" };
|
|
362
372
|
if (!clearing) for (const k of ["tagline", "bio"]) if (cardText[k] !== before[k]) draft[`card_${k}`] = cardText[k];
|
|
373
|
+
if (!clearing && focus.mine !== focus.start) draft.card_banner_focus = focus.mine; // #2086
|
|
363
374
|
const cardPics = {};
|
|
364
375
|
try {
|
|
365
376
|
// A picture too large is fitted first (after asking) — before anything is written, so declining writes nothing.
|
|
@@ -422,10 +433,19 @@ function EditProfile({ it, onClose }) {
|
|
|
422
433
|
<label class="field"><span>${tn("cardTagline")}</span><input value=${cardText.tagline} onInput=${(e) => setCardText({ ...cardText, tagline: e.target.value })} /></label>
|
|
423
434
|
<label class="field"><span>${tn("cardBio")}</span><textarea rows="4" value=${cardText.bio} onInput=${(e) => setCardText({ ...cardText, bio: e.target.value })}></textarea></label>
|
|
424
435
|
${["portrait", "banner"].map((slot) => html`<div key=${slot} class="form">
|
|
425
|
-
<label class="field"><span>${tn(slot === "portrait" ? "cardPortrait" : "cardBanner")}</span><input ref=${pics[slot]} type="file" accept="image/png,image/jpeg,image/webp"
|
|
436
|
+
<label class="field"><span>${tn(slot === "portrait" ? "cardPortrait" : "cardBanner")}</span><input ref=${pics[slot]} type="file" accept="image/png,image/jpeg,image/webp"
|
|
437
|
+
onChange=${slot === "banner" ? (e) => { const fl = e.currentTarget.files && e.currentTarget.files[0]; if (fl) preview.fromFile(fl); } : undefined} /></label>
|
|
426
438
|
${card && card.mine && card.mine[slot] ? html`<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${removeCard[slot] ? "true" : "false"}
|
|
427
439
|
onClick=${() => setRemoveCard({ ...removeCard, [slot]: !removeCard[slot] })}>${tn(removeCard[slot] ? "cardKeep" : "cardRemove")}</button></div>` : null}
|
|
428
440
|
</div>`)}
|
|
441
|
+
${bannerPreview ? html`<div class="field bf-field">
|
|
442
|
+
<span>${tn("cardBannerFocus")}</span>
|
|
443
|
+
<${BannerFocusPreview} src=${bannerPreview.src} focus=${focus.value} />
|
|
444
|
+
<input type="range" min="0" max="100" step="1" value=${focus.value} aria-label=${tn("cardBannerFocus")}
|
|
445
|
+
onInput=${(e) => { const v = Number(e.currentTarget.value); setFocus({ ...focus, value: v, mine: v }); }} />
|
|
446
|
+
<span class="note">${tn("cardBannerFocusNote")}</span>
|
|
447
|
+
${focus.mine !== null ? html`<div class="field-row"><button type="button" class="btn btn-sm" onClick=${() => setFocus({ ...focus, value: focus.agent ?? 50, mine: null })}>${tn("cardFocusFollow")}</button></div>` : null}
|
|
448
|
+
</div>` : null}
|
|
429
449
|
</fieldset>
|
|
430
450
|
<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${clearCard ? "true" : "false"} onClick=${() => setClearCard(!clearCard)}>${tn(clearCard ? "keepCard" : "clearCard")}</button>
|
|
431
451
|
${clearCard ? html`<span class="muted">${tn("clearCardNote")}</span>` : null}</div>
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
* surfaces it is used on, and focus rings and strong borders meet 3:1 (tests/web-tokens-1408.test.ts).
|
|
10
10
|
*
|
|
11
11
|
* Inter is bundled (/assets/inter.woff2, SIL OFL 1.1 — see fonts/README.md): the CSP allows fonts from 'self' only,
|
|
12
|
-
* and nothing is loaded from another site. Chinese falls back to the system's CJK fonts, in the order below.
|
|
12
|
+
* and nothing is loaded from another site. Chinese falls back to the system's CJK fonts, in the order below.
|
|
13
|
+
*
|
|
14
|
+
* Emoji (#1918 follow-up, the user's tofu boxes): every stack ends with the platforms' colour emoji fonts and the
|
|
15
|
+
* `emoji` generic (--font-emoji), so an emoji the text fonts lack is drawn by Windows' Segoe UI Emoji, macOS's Apple
|
|
16
|
+
* Color Emoji or Linux's Noto Color Emoji rather than as a box — for the stock stacks and each theme font
|
|
17
|
+
* (app.css: .th-font-system, .th-font-mono). A browser tries the families in order for each character. */
|
|
13
18
|
|
|
14
19
|
@font-face {
|
|
15
20
|
font-family: "Inter";
|
|
@@ -22,8 +27,9 @@
|
|
|
22
27
|
|
|
23
28
|
:root {
|
|
24
29
|
/* ── Type ── */
|
|
25
|
-
--font-
|
|
26
|
-
--font-
|
|
30
|
+
--font-emoji: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji;
|
|
31
|
+
--font-ui: Inter, "PingFang TC", "PingFang SC", "Noto Sans TC", "Noto Sans SC", "Noto Sans CJK TC", "Noto Sans CJK SC", "Microsoft JhengHei", "Microsoft YaHei", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, var(--font-emoji);
|
|
32
|
+
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace, var(--font-emoji);
|
|
27
33
|
--fs-xs: 12px; --lh-xs: 16px;
|
|
28
34
|
--fs-sm: 13px; --lh-sm: 18px;
|
|
29
35
|
--fs-md: 15px; --lh-md: 24px; /* message text */
|
|
@@ -37,8 +37,10 @@ const P = {
|
|
|
37
37
|
type: () => html`<path d="M4 7V5h16v2M9 19h6M12 5v14"/>`,
|
|
38
38
|
chart: () => html`<path d="M3 20h18M6 16v-4M11 16V8M16 16v-6"/>`,
|
|
39
39
|
up: () => html`<path d="M12 19V5M5 12l7-7 7 7"/>`,
|
|
40
|
+
sort: () => html`<path d="M7 4v16M3 16l4 4 4-4M17 20V4M13 8l4-4 4 4"/>`, // #2179: the sidebar's sort
|
|
40
41
|
star: () => html`<path d="M12 2.5l2.94 5.96 6.56.95-4.75 4.63 1.12 6.54L12 17.5l-5.87 3.08 1.12-6.54L2.5 9.41l6.56-.95L12 2.5z"/>`,
|
|
41
42
|
eye: () => html`<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>`,
|
|
43
|
+
idcard: () => html`<rect x="2" y="5" width="20" height="14" rx="2"/><circle cx="8" cy="11" r="2"/><path d="M5 16a3 3 0 0 1 6 0M14 10h5M14 14h3"/>`,
|
|
42
44
|
check: () => html`<path d="M20 6L9 17l-5-5"/>`,
|
|
43
45
|
inbox: () => html`<path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>`,
|
|
44
46
|
bell: () => html`<path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>`,
|
|
@@ -4,21 +4,25 @@ import { html, useEffect, useRef, useState } from "./app-html.js";
|
|
|
4
4
|
import { Icon } from "./ui-icons.js";
|
|
5
5
|
import { t } from "./app-i18n.js";
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* items: [{ key, label, icon?, onSelect, danger?, disabled? }] (falsy entries are skipped). `checked` makes a radio item;
|
|
9
|
+
* `checkbox: true` with `checked` a checkbox item (#2217 hotfix: the chat's card menu), which toggles and keeps the menu
|
|
10
|
+
* open. `active`: the button shows a pressed look (what it controls is on), without changing its role.
|
|
11
|
+
*/
|
|
12
|
+
export function Menu({ items, label, icon = "more", cls = "", active = false }) {
|
|
9
13
|
const [open, setOpen] = useState(false);
|
|
10
14
|
const btn = useRef(null), list = useRef(null);
|
|
11
15
|
const shown = (items || []).filter(Boolean);
|
|
12
16
|
useEffect(() => {
|
|
13
17
|
if (!open) return undefined;
|
|
14
|
-
const first = list.current && list.current.querySelector("[role=menuitem]:not([disabled])");
|
|
18
|
+
const first = list.current && list.current.querySelector("[role=menuitem]:not([disabled]), [role=menuitemradio]:not([disabled]), [role=menuitemcheckbox]:not([disabled])");
|
|
15
19
|
if (first) first.focus();
|
|
16
20
|
const outside = (e) => { if (list.current && !list.current.contains(e.target) && btn.current && !btn.current.contains(e.target)) setOpen(false); };
|
|
17
21
|
document.addEventListener("pointerdown", outside, true);
|
|
18
22
|
return () => document.removeEventListener("pointerdown", outside, true);
|
|
19
23
|
}, [open]);
|
|
20
24
|
function onKey(e) {
|
|
21
|
-
const all = [...list.current.querySelectorAll("[role=menuitem]:not([disabled])")];
|
|
25
|
+
const all = [...list.current.querySelectorAll("[role=menuitem]:not([disabled]), [role=menuitemradio]:not([disabled]), [role=menuitemcheckbox]:not([disabled])")];
|
|
22
26
|
const i = all.indexOf(document.activeElement);
|
|
23
27
|
if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); setOpen(false); btn.current && btn.current.focus(); }
|
|
24
28
|
else if (e.key === "ArrowDown") { e.preventDefault(); (all[(i + 1) % all.length] || all[0])?.focus(); }
|
|
@@ -29,11 +33,20 @@ export function Menu({ items, label, icon = "more", cls = "" }) {
|
|
|
29
33
|
}
|
|
30
34
|
function choose(item) { setOpen(false); btn.current && btn.current.focus(); item.onSelect(); }
|
|
31
35
|
return html`<div class=${`menu ${cls}`}>
|
|
32
|
-
<button ref=${btn} type="button" class
|
|
36
|
+
<button ref=${btn} type="button" class=${`icon-btn${active ? " active" : ""}`} aria-haspopup="menu" aria-expanded=${open ? "true" : "false"}
|
|
33
37
|
aria-label=${label || t("app.more")} title=${label || t("app.more")} onClick=${() => setOpen(!open)}><${Icon} name=${icon} /></button>
|
|
34
38
|
${open ? html`<div data-trust="menu" ref=${list} class="menu-list" role="menu" aria-label=${label || t("app.more")} onKeyDown=${onKey}>
|
|
35
|
-
${shown.map(item =>
|
|
36
|
-
|
|
39
|
+
${shown.map(item => item.heading
|
|
40
|
+
? html`<div key=${item.key} class="menu-heading" role="presentation">${item.label}</div>`
|
|
41
|
+
: item.checkbox
|
|
42
|
+
? html`<button key=${item.key} type="button" role="menuitemcheckbox" aria-checked=${item.checked ? "true" : "false"} class="menu-item menu-radio"
|
|
43
|
+
disabled=${!!item.disabled} onClick=${() => item.onSelect()}><span class="menu-tick" aria-hidden="true">${item.checked ? html`<${Icon} name="check" size=${16} />` : null}</span><span>${item.label}</span></button>`
|
|
44
|
+
: item.checked !== undefined
|
|
45
|
+
// #2196: one of a group of choices (Page colours): a radio item, the chosen one ticked.
|
|
46
|
+
? html`<button key=${item.key} type="button" role="menuitemradio" aria-checked=${item.checked ? "true" : "false"} class="menu-item menu-radio"
|
|
47
|
+
disabled=${!!item.disabled} onClick=${() => choose(item)}><span class="menu-tick" aria-hidden="true">${item.checked ? html`<${Icon} name="check" size=${16} />` : null}</span><span>${item.label}</span></button>`
|
|
48
|
+
: html`<button key=${item.key} type="button" role="menuitem" class=${`menu-item${item.danger ? " danger" : ""}`}
|
|
49
|
+
disabled=${!!item.disabled} onClick=${() => choose(item)}>${item.icon ? html`<${Icon} name=${item.icon} size=${16} />` : null}<span>${item.label}</span></button>`)}
|
|
37
50
|
</div>` : null}
|
|
38
51
|
</div>`;
|
|
39
52
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Toasts: one stack in a corner (under the header on a phone), each gone after a few
|
|
2
|
-
|
|
1
|
+
// Toasts: one stack in a corner (under the header on a phone; above the tabs on the task board), each gone after a few
|
|
2
|
+
// seconds. Text only. A manual popover, so it stands in the top layer above an open dialog (#2299).
|
|
3
|
+
import { html, useEffect, useRef, useState } from "./app-html.js";
|
|
3
4
|
import { createStore } from "./app-store.js";
|
|
4
5
|
|
|
5
6
|
const toasts = createStore([]);
|
|
@@ -14,5 +15,17 @@ export function toast(text, ok = true) {
|
|
|
14
15
|
export function Toasts() {
|
|
15
16
|
const [list, setList] = useState(toasts.get());
|
|
16
17
|
useEffect(() => toasts.subscribe(setList), []);
|
|
17
|
-
|
|
18
|
+
// #2299: a manual popover — in the top layer, so an open modal dialog's backdrop does not dim a toast (it did: grey on
|
|
19
|
+
// grey in the dark theme). Shown again on every new toast, so it is above a dialog opened since. Without popover
|
|
20
|
+
// support it stays an ordinary fixed box, as before.
|
|
21
|
+
const box = useRef(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const el = box.current;
|
|
24
|
+
if (!el || typeof el.showPopover !== "function") return;
|
|
25
|
+
try {
|
|
26
|
+
if (el.matches(":popover-open")) el.hidePopover();
|
|
27
|
+
if (list.length) el.showPopover();
|
|
28
|
+
} catch { /* a popover that cannot be shown stays where it is */ }
|
|
29
|
+
}, [list]);
|
|
30
|
+
return html`<div ref=${box} popover="manual" data-trust="toast" class="toasts" role="status" aria-live="polite">${list.map(x => html`<div key=${x.id} class=${`toast ${x.ok ? "ok" : "err"}`}>${x.text}</div>`)}</div>`;
|
|
18
31
|
}
|