@songsid/agend 2.3.0-beta.25 → 2.3.0-beta.26
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/dist/adapter-world.js.map +1 -1
- package/dist/agend-home.d.ts +5 -0
- package/dist/agend-home.js +7 -0
- package/dist/agend-home.js.map +1 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +8 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +1 -1
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/apply-job.js.map +1 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +5 -0
- package/dist/auth-api.js +21 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +4 -0
- package/dist/authority/authority-state.js +17 -8
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +14 -6
- package/dist/authority/web-authority.js +19 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.d.ts +2 -2
- package/dist/backend/antigravity.js +2 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-package.js.map +1 -1
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.js +1 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-kas-store.js.map +1 -1
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/mock.js.map +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/session-signals.js.map +1 -1
- package/dist/backend-availability.js.map +1 -1
- package/dist/backend-command.js.map +1 -1
- package/dist/backend-outage.js.map +1 -1
- package/dist/binary-probe.js.map +1 -1
- package/dist/bug-report.js.map +1 -1
- package/dist/bundled-skills.js.map +1 -1
- package/dist/cache-service.js +1 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.js +29 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +54 -14
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +10 -1
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/sticker-download.js.map +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/classic-channel-manager.js +3 -2
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/cli-flags.js.map +1 -1
- package/dist/cli.js +15 -9
- package/dist/cli.js.map +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.js.map +1 -1
- package/dist/completion-install.js.map +1 -1
- package/dist/completion.js.map +1 -1
- package/dist/config-validator.js +18 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +8 -2
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.js.map +1 -1
- package/dist/context-guardian.js.map +1 -1
- package/dist/coredump-filter.js.map +1 -1
- package/dist/cpu-profile.js.map +1 -1
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +73 -3
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.js.map +1 -1
- package/dist/darwin-memory.js.map +1 -1
- package/dist/dashboard-live.d.ts +8 -0
- package/dist/dashboard-live.js +35 -0
- package/dist/dashboard-live.js.map +1 -0
- package/dist/dashboard-permissions.d.ts +69 -0
- package/dist/dashboard-permissions.js +138 -0
- package/dist/dashboard-permissions.js.map +1 -0
- package/dist/decision-list.js.map +1 -1
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/delivery-reconciliation.test.js.map +1 -1
- package/dist/diagnostic-redaction.js.map +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/e2e-isolation.js.map +1 -1
- package/dist/event-log.js.map +1 -1
- package/dist/export-import.js +3 -2
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-label.js.map +1 -1
- package/dist/fleet-lock.js +3 -2
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +76 -6
- package/dist/fleet-manager.js +479 -145
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.js +3 -2
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-startup.d.ts +14 -0
- package/dist/fleet-startup.js +30 -0
- package/dist/fleet-startup.js.map +1 -0
- package/dist/fleet-traffic.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +0 -20
- package/dist/forged-envelope-scan.js +3 -2
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +2 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +2 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +33 -11
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +42 -13
- package/dist/host-memory.js.map +1 -1
- package/dist/instance-config-impact.js +6 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +1 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.js +6 -2
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.js.map +1 -1
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-log-gate.js.map +1 -1
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +22 -0
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.js.map +1 -1
- package/dist/logger.js +3 -2
- package/dist/logger.js.map +1 -1
- package/dist/login-controller.js.map +1 -1
- package/dist/login-manager.js.map +1 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/memory-pressure.js.map +1 -1
- package/dist/muse-usage-relay.js.map +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.js.map +1 -1
- package/dist/network-family.js.map +1 -1
- package/dist/operator-control-paths.js +3 -4
- package/dist/operator-control-paths.js.map +1 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +68 -68
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.js.map +1 -1
- package/dist/pane-context-cache.js.map +1 -1
- package/dist/paths.js.map +1 -1
- package/dist/private-dir.js.map +1 -1
- package/dist/probe-worker-pool.js.map +1 -1
- package/dist/profile-control.js.map +1 -1
- package/dist/prompt-file-ref.js.map +1 -1
- package/dist/provider-probe.js.map +1 -1
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-link-auto-open.d.ts +32 -0
- package/dist/public-link-auto-open.js +86 -0
- package/dist/public-link-auto-open.js.map +1 -0
- package/dist/public-link-owner.d.ts +10 -0
- package/dist/public-link-owner.js +36 -0
- package/dist/public-link-owner.js.map +1 -0
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.js.map +1 -1
- package/dist/public-web-gateway.js +4 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +35 -1
- package/dist/public-web-link.js +142 -43
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +5 -0
- package/dist/quickstart.js +25 -16
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/resource-report.js.map +1 -1
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.js.map +1 -1
- package/dist/runtime-cpu-profile.js.map +1 -1
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/task-board-text.js +16 -11
- package/dist/scheduler/task-board-text.js.map +1 -1
- package/dist/scheduler/task-board.d.ts +39 -11
- package/dist/scheduler/task-board.js +123 -45
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/scheduler/types.d.ts +1 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-index.js.map +1 -1
- package/dist/secret-file.js.map +1 -1
- package/dist/secret-store.js.map +1 -1
- package/dist/service-installer.d.ts +7 -1
- package/dist/service-installer.js +13 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +33 -4
- package/dist/service-plan.js +177 -8
- package/dist/service-plan.js.map +1 -1
- package/dist/session-oom.d.ts +21 -0
- package/dist/session-oom.js +139 -0
- package/dist/session-oom.js.map +1 -0
- package/dist/settings-api.js +3 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +71 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +4 -0
- package/dist/settings-confirmation.js +2 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +4 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-instance-patch.js +13 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-host.d.ts +5 -1
- package/dist/setup-host.js +8 -1
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +5 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +4 -0
- package/dist/spawn-gate.js +15 -5
- package/dist/spawn-gate.js.map +1 -1
- package/dist/startup-memory-deadline.d.ts +23 -0
- package/dist/startup-memory-deadline.js +55 -0
- package/dist/startup-memory-deadline.js.map +1 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/storm-window.js.map +1 -1
- package/dist/systemd-peer-bus.js.map +1 -1
- package/dist/target-queue-worker.js.map +1 -1
- package/dist/task-board-summary.js.map +1 -1
- package/dist/task-board-tool.js +3 -2
- package/dist/task-board-tool.js.map +1 -1
- package/dist/task-board-waker.js +8 -4
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.d.ts +1 -0
- package/dist/tmux-manager.js +2 -1
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-archiver.js.map +1 -1
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.js.map +1 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.js +3 -2
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/tunnel/public-fetch.js.map +1 -1
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +20 -1
- package/dist/ui/chat-render.js +1 -15
- package/dist/ui/chat-store.js +171 -61
- package/dist/ui/chat-strings.js +7 -2
- package/dist/ui/chat-thread.js +48 -1
- package/dist/ui/fleet-canvas-layout.js +3 -0
- package/dist/ui/fleet-canvas.js +23 -20
- package/dist/ui/fleet-office-editor.js +149 -0
- package/dist/ui/fleet-office-model.js +358 -0
- package/dist/ui/fleet-office-pack.js +199 -0
- package/dist/ui/fleet-office-skin.js +474 -0
- package/dist/ui/fleet-office-store.js +49 -0
- package/dist/ui/fleet-office-zones.js +177 -0
- package/dist/ui/fleet-office.js +434 -0
- package/dist/ui/fleet-org.js +18 -5
- package/dist/ui/guest-refused.html +41 -0
- package/dist/ui/image-lightbox.js +9 -3
- package/dist/ui/panel-chat.js +14 -13
- package/dist/ui/panel-settings.js +24 -7
- package/dist/ui/settings-dialogs.js +11 -2
- package/dist/ui/settings-model.js +20 -0
- package/dist/ui/settings-strings.js +36 -0
- package/dist/ui/settings-web-authority.js +7 -2
- package/dist/ui/shared/agend-auth.js +2 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-prefs.js +2 -0
- package/dist/ui/shared/app-shell.js +12 -0
- package/dist/ui/shared/app-store.js +1 -0
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +132 -3
- package/dist/ui/shared/guest-refused.js +35 -0
- package/dist/ui/shared/header-tools.js +2 -2
- package/dist/ui/shared/instance-nav.js +25 -8
- package/dist/ui/shared/ui-dialog.js +2 -2
- package/dist/ui/shared/usage-dialog.js +152 -6
- package/dist/ui/shared/view-strings.js +20 -0
- package/dist/ui/shared/web-authorize.js +3 -1
- package/dist/update-check.js.map +1 -1
- package/dist/update-dispatch.js.map +1 -1
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.js.map +1 -1
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/providers.d.ts +8 -0
- package/dist/usage/providers.js +6 -2
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/usage/usage-api.js +2 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.js +1 -1
- package/dist/usage-rollup.js.map +1 -1
- package/dist/user-systemd.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-ambient.js.map +1 -1
- package/dist/web-api.d.ts +15 -2
- package/dist/web-api.js +174 -25
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon.js.map +1 -1
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.js +5 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-card.js.map +1 -1
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +41 -11
- package/dist/web-chat-history.js +93 -22
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.js.map +1 -1
- package/dist/web-effect-bundle.d.ts +2 -2
- package/dist/web-effect-library.js.map +1 -1
- package/dist/web-effect-playback.js.map +1 -1
- package/dist/web-effect-profiles.d.ts +16 -16
- package/dist/web-effect-profiles.js +3 -2
- package/dist/web-effect-profiles.js.map +1 -1
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect.d.ts +7 -7
- package/dist/web-effect.js.map +1 -1
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.js +1 -1
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-guest-rooms.js.map +1 -1
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-icons.js.map +1 -1
- package/dist/web-identity-color.js.map +1 -1
- package/dist/web-images.js.map +1 -1
- package/dist/web-libavoid.js.map +1 -1
- package/dist/web-login.js.map +1 -1
- package/dist/web-mentions.js.map +1 -1
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-office-store.d.ts +104 -0
- package/dist/web-office-store.js +236 -0
- package/dist/web-office-store.js.map +1 -0
- package/dist/web-owner-css-store.js.map +1 -1
- package/dist/web-owner-css.js.map +1 -1
- package/dist/web-png.js.map +1 -1
- package/dist/web-prefs.js +2 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-profile.js.map +1 -1
- package/dist/web-reactions.d.ts +2 -1
- package/dist/web-request-context.d.ts +5 -0
- package/dist/web-request-context.js.map +1 -1
- package/dist/web-returning-device.js +1 -1
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-schedules.js.map +1 -1
- package/dist/web-send-dedupe.d.ts +36 -0
- package/dist/web-send-dedupe.js +69 -0
- package/dist/web-send-dedupe.js.map +1 -0
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.js +1 -1
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -4
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-terminal.js.map +1 -1
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-upload.d.ts +5 -1
- package/dist/web-upload.js +6 -4
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.js.map +1 -1
- package/dist/webhook-emitter.js.map +1 -1
- package/dist/windows-shortcuts.js +1 -1
- package/dist/windows-shortcuts.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/dist/ws-server.js.map +1 -1
- package/dist/yaml-shared-nodes.js.map +1 -1
- package/launcher/agend-install.cjs +281 -6
- package/launcher/private-home.cjs +43 -0
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +1 -1
|
@@ -50,6 +50,7 @@ export function applyStatus(d) {
|
|
|
50
50
|
if (i.state === "awaiting_input") awaiting[i.name] = typeof i.interaction_summary === "string" ? i.interaction_summary : "";
|
|
51
51
|
}
|
|
52
52
|
appStore.set({ canvasWorker: d.canvas_worker === true, ready: true, instances: d.instances, uptime: d.uptime ?? 0, exec, awaiting,
|
|
53
|
+
startup: d.startup?.phase === "starting" ? d.startup : null,
|
|
53
54
|
connections: typeof d.connections === "number" ? d.connections : null,
|
|
54
55
|
// #1665: the host preferences' revision (app-prefs.js re-reads them when it moves).
|
|
55
56
|
prefsRev: typeof d.prefs_rev === "number" ? d.prefs_rev : null,
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
// org canvas reads /ui/traffic when it has no stream).
|
|
35
35
|
// #2233: backend_login — a dashboard sign-in started, moved or ended (no details: the page reads its own view).
|
|
36
36
|
// tasks — the task board changed: the card numbers a write touched (SSE only: the board reads again when it is shown).
|
|
37
|
-
|
|
37
|
+
// offices — the stored offices changed ({ rev }; SSE only: the Office view reads /ui/offices again).
|
|
38
|
+
const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "reactions", "steps_tick", "clock", "traffic", "delegation", "backend_login", "tasks", "offices"];
|
|
38
39
|
const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
|
|
39
40
|
const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
|
|
40
41
|
const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -470,6 +470,36 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
470
470
|
such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
|
|
471
471
|
the right under its meta, and a long one still wraps at the message's max-width. */
|
|
472
472
|
.msg.user .body { background: var(--user-bubble); border-radius: var(--r-xl); padding: 10px var(--s-4); width: fit-content; max-width: 100%; margin-left: auto; }
|
|
473
|
+
/* A message this page is sending (chat-thread.js outgoingHtml): shown at once, its text dimmed with "Sending…" until the
|
|
474
|
+
server takes it; failed, a straight red bar with Retry and Delete. Each file: a picture as itself (or a document mark),
|
|
475
|
+
its name, a square-ended bar and its percentage, moved in place. */
|
|
476
|
+
.msg.user.out .meta { align-items: center; gap: var(--s-2); }
|
|
477
|
+
.msg.out.out-queued .body, .msg.out.out-sending .body { color: var(--text-3); }
|
|
478
|
+
.msg.out.out-queued .body .md, .msg.out.out-sending .body .md { opacity: .72; }
|
|
479
|
+
.out-state { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-3); }
|
|
480
|
+
.out-spin { width: 10px; height: 10px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: out-spin .8s linear infinite; }
|
|
481
|
+
@keyframes out-spin { to { transform: rotate(360deg); } }
|
|
482
|
+
.out-files { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-2); }
|
|
483
|
+
.msg.out .body .out-files:first-child { margin-top: 0; }
|
|
484
|
+
.out-file { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
|
|
485
|
+
.out-pic { width: 72px; height: 72px; object-fit: cover; border-radius: var(--r-md); flex: none; background: var(--surface-2); }
|
|
486
|
+
.msg.out.out-queued .out-pic, .msg.out.out-sending .out-pic { opacity: .7; }
|
|
487
|
+
.out-doc { width: 32px; height: 40px; flex: none; border: 1px solid var(--border); border-radius: 4px; background: var(--surface-2); }
|
|
488
|
+
.out-file-info { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px var(--s-2); min-width: 0; flex: 1; min-width: 140px; }
|
|
489
|
+
.out-name { font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
490
|
+
.out-pct { font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }
|
|
491
|
+
.out-bar { grid-column: 1 / -1; height: 3px; background: var(--border); overflow: hidden; }
|
|
492
|
+
.out-fill { display: block; height: 100%; background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform .2s linear; }
|
|
493
|
+
.out-file[data-state="done"] .out-bar, .out-file[data-state="done"] .out-pct { visibility: hidden; }
|
|
494
|
+
.out-file[data-state="failed"] .out-fill { background: var(--danger); }
|
|
495
|
+
.out-file[data-state="failed"] .out-pct { color: var(--danger); }
|
|
496
|
+
.msg.out.out-sent .out-file .out-bar, .msg.out.out-sent .out-file .out-pct { visibility: hidden; }
|
|
497
|
+
/* Hugs its words under the bubble's right edge, as the bubble does: the bar stays beside them, never across the row. */
|
|
498
|
+
.out-note { --lbar-color: var(--danger); display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); width: fit-content; max-width: 100%;
|
|
499
|
+
margin: var(--s-2) 0 0 auto; padding: var(--s-1) 0 var(--s-1) calc(var(--s-2) + 3px); font-size: var(--fs-xs); color: var(--danger); }
|
|
500
|
+
.out-error { overflow-wrap: anywhere; }
|
|
501
|
+
.out-actions { display: inline-flex; gap: var(--s-1); }
|
|
502
|
+
@media (prefers-reduced-motion: reduce) { .out-spin { animation: none; border-right-color: currentColor; opacity: .6; } .out-fill { transition: none; } }
|
|
473
503
|
/* A person's click on a reply button: the bubble shows 🔘 and the button's label (never the value the agent was given);
|
|
474
504
|
live, the label is a button that shows the reply its buttons were on. */
|
|
475
505
|
.click-chip { display: flex; }
|
|
@@ -795,6 +825,8 @@ img.amb-p { object-fit: contain; }
|
|
|
795
825
|
/* #1939 S2a: Settings → Web → Web authority. */
|
|
796
826
|
.s-web-authority .s-va-sessions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
|
|
797
827
|
.s-web-authority .s-va-sessions li { padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-sm); overflow-wrap: anywhere; }
|
|
828
|
+
/* #2453: "over your named link" stays one phrase (the list item may break anywhere, CJK included). */
|
|
829
|
+
.s-web-authority .s-va-named { white-space: nowrap; }
|
|
798
830
|
.s-web-authority .s-va-warn { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-line); border-radius: var(--r-sm); padding: var(--s-1) var(--s-2); }
|
|
799
831
|
.s-web-authority .save-row { flex-wrap: wrap; }
|
|
800
832
|
/* #1920: a guest's note under the composer — on a phone too, where the key hints are hidden. */
|
|
@@ -1032,7 +1064,21 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
1032
1064
|
down, never the count; the count is as tall as a header, so the row does not grow. */
|
|
1033
1065
|
.nav-facets { display: flex; align-items: flex-start; gap: var(--s-2); padding: var(--s-2) var(--s-1) 0; }
|
|
1034
1066
|
.nav-facet-groups { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-1) var(--s-2); flex: 1 1 auto; min-width: 0; }
|
|
1035
|
-
|
|
1067
|
+
/* #2401: the count, "shown" under it and Clear under that. The word's width is held by ::before (its text from
|
|
1068
|
+
data-reserve) and idle Clear's by the button itself, both at no height, unseen and out of reach (visibility): the column —
|
|
1069
|
+
and the headers' box beside it — is as wide with a facet set as without. */
|
|
1070
|
+
.nav-facet-end { flex: none; margin: 0 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; }
|
|
1071
|
+
.nav-facets .nav-shown { margin: 0; padding: 2px 0; white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
|
|
1072
|
+
.nav-shown-word { display: block; }
|
|
1073
|
+
.nav-facet-end::before { content: attr(data-reserve); display: block; height: 0; overflow: hidden; visibility: hidden; font-size: var(--fs-xs); white-space: nowrap; }
|
|
1074
|
+
.nav-facet-end .is-idle { visibility: hidden; height: 0; min-height: 0; margin: 0; padding-top: 0; padding-bottom: 0; border-top-width: 0; border-bottom-width: 0; overflow: hidden; }
|
|
1075
|
+
/* No open list beside it (.compact): one cell of one line, as wide as the count or Clear; a facet set, Clear in the count's
|
|
1076
|
+
place (the count transparent, still read; the word for a screen reader only, so its width is not held here). */
|
|
1077
|
+
.nav-facets.compact .nav-facet-end { display: grid; justify-items: end; align-items: center; }
|
|
1078
|
+
.nav-facets.compact .nav-facet-end > * { grid-area: 1 / 1; margin: 0; }
|
|
1079
|
+
.nav-facets.compact .nav-facet-end::before { display: none; }
|
|
1080
|
+
.nav-facets.compact .nav-shown.is-set { opacity: 0; pointer-events: none; }
|
|
1081
|
+
.nav-facets.compact .nav-shown-word { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
1036
1082
|
.nav-facet { font-size: var(--fs-xs); color: var(--text-2); min-width: 0; max-width: 100%; }
|
|
1037
1083
|
/* #1700: a facet group's header — the whole row toggles its list; folded with a filter set, a short summary. */
|
|
1038
1084
|
.nav-facet-head { display: inline-flex; align-items: center; gap: var(--s-1); max-width: 100%; padding: 2px var(--s-2); border: 0; border-radius: var(--r-sm);
|
|
@@ -1041,10 +1087,15 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
1041
1087
|
.nav-facet-head:focus-visible { outline: 2px solid var(--accent); }
|
|
1042
1088
|
.nav-facet-head .caret { color: var(--text-3); flex: none; }
|
|
1043
1089
|
.nav-facet-sum { min-width: 0; max-width: 12em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--accent); }
|
|
1090
|
+
/* #2401: the summary takes only the room its line has left — never counted when the headers wrap (contain: inline-size,
|
|
1091
|
+
its gap taken back), so a facet set never sends a header to a line of its own; the header holding it grows into the rest. */
|
|
1092
|
+
.nav-facet.has-sum { flex-grow: 1; }
|
|
1093
|
+
.nav-facet.has-sum .nav-facet-head { width: 100%; }
|
|
1094
|
+
.nav-facet-sum { contain: inline-size; flex: 1 1 0; margin-left: calc(-1 * var(--s-1)); padding-left: var(--s-1); }
|
|
1044
1095
|
.nav-facet-n { display: inline-block; min-width: 16px; padding: 0 4px; border-radius: 8px; background: var(--accent); color: var(--on-accent, #fff); text-align: center; font-variant-numeric: tabular-nums; }
|
|
1045
1096
|
.nav-facet-list { display: flex; flex-direction: column; gap: 2px; padding: var(--s-1) var(--s-2); }
|
|
1046
1097
|
.nav-chip { display: flex; align-items: center; gap: var(--s-2); cursor: pointer; color: var(--text); }
|
|
1047
|
-
.nav-facet-reset { font-size: var(--fs-xs); }
|
|
1098
|
+
.nav-facet-reset { font-size: var(--fs-xs); flex: none; white-space: nowrap; margin-top: 2px; padding: 2px var(--s-1); }
|
|
1048
1099
|
|
|
1049
1100
|
/* One gap on every page (View's, #1448): the header's controls are the same size on every tab. */
|
|
1050
1101
|
.panel-actions .btn { gap: var(--s-1); }
|
|
@@ -1102,7 +1153,42 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
1102
1153
|
/* #1673: the usage rail beside the page on a wide screen (header-tools.js UsageRail; shown only at min-width 1200px). */
|
|
1103
1154
|
.u-rail { width: 320px; flex-shrink: 0; display: flex; flex-direction: column; min-height: 0; border-left: 1px solid var(--border); background: var(--bg); }
|
|
1104
1155
|
.u-rail-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); padding: var(--s-3) var(--s-3) var(--s-2) var(--s-4); }
|
|
1105
|
-
.u-rail-head h2 { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
|
|
1156
|
+
.u-rail-head h2 { margin: 0; flex: 1; min-width: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
|
|
1157
|
+
/* The usage view's compact mode (usage-dialog.js CompactGroup / CompactRow), the user's pick (2026-10-22): the logins
|
|
1158
|
+
grouped by provider (its name once, in small capitals), each in three short lines — its name and plan with the
|
|
1159
|
+
soonest reset on the right, then each window as a labelled full-width bar and the percent. */
|
|
1160
|
+
.dlg-tools { display: inline-flex; align-items: center; flex: none; }
|
|
1161
|
+
.u-compact-toggle { flex: none; }
|
|
1162
|
+
/* flex: none — a group never shrinks to fit the rail (it would clip its accounts): the rail scrolls instead. */
|
|
1163
|
+
.u-cgroup { flex: none; display: flex; flex-direction: column; border-radius: var(--r-md); background: var(--surface); overflow: hidden; }
|
|
1164
|
+
.u-cg-head { display: flex; align-items: baseline; gap: 4px; padding: 7px var(--s-3) 3px; font-size: 11px; font-weight: var(--fw-semibold);
|
|
1165
|
+
letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-3); }
|
|
1166
|
+
.u-cg-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1167
|
+
.u-cg-count { flex: none; }
|
|
1168
|
+
.u-crow { display: flex; flex-direction: column; gap: 3px; width: 100%; min-width: 0; padding: 6px var(--s-3); border: 0; border-top: 1px solid var(--border);
|
|
1169
|
+
background: transparent; color: inherit; font: inherit; line-height: 1.3; text-align: left; cursor: pointer; }
|
|
1170
|
+
.u-cg-head + .u-crow { border-top: 0; }
|
|
1171
|
+
.u-crow:hover { background: var(--surface-2); }
|
|
1172
|
+
.u-crow:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--focus); }
|
|
1173
|
+
/* The name never gives way to the plan (#2415); the reset keeps its room on the right. */
|
|
1174
|
+
.u-crow-head { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: var(--fs-sm); }
|
|
1175
|
+
.u-crow-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--fw-semibold); color: var(--text); }
|
|
1176
|
+
.u-crow .u-plan { flex: 0 100 auto; min-width: 22px; max-width: max-content; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1177
|
+
padding-block: 0; line-height: 16px; font-size: 10.5px; }
|
|
1178
|
+
.u-crow-reset { margin-left: auto; flex: none; padding-left: 6px; font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
1179
|
+
.u-cm { display: grid; grid-template-columns: 2.6em minmax(0, 1fr) 3.4em; align-items: center; gap: 7px; min-width: 0; }
|
|
1180
|
+
.u-cm-tag { font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1181
|
+
/* #2240: an account of several pools (agy) names each pool's line: a wider label, ending in an ellipsis if it must. */
|
|
1182
|
+
.u-crow.is-pooled .u-cm { grid-template-columns: minmax(0, 7.5em) minmax(0, 1fr) 3.4em; }
|
|
1183
|
+
.u-cm-bar { height: 6px; border-radius: var(--r-full); background: var(--surface-2); overflow: hidden; }
|
|
1184
|
+
.u-cm-fill { display: block; height: 100%; width: 0; min-width: 2px; border-radius: var(--r-full); background: var(--accent); }
|
|
1185
|
+
.u-cm-fill.warn { background: var(--warn); } .u-cm-fill.crit { background: var(--danger); }
|
|
1186
|
+
.u-cm-pct { text-align: right; font-size: 13px; font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; color: var(--text); }
|
|
1187
|
+
.u-cm-pct.warn { color: var(--warn); } .u-cm-pct.crit { color: var(--danger); }
|
|
1188
|
+
.u-crow-note { font-size: var(--fs-xs); color: var(--text-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1189
|
+
.u-crow-note.is-alert::before { content: "●"; margin-right: 5px; color: var(--danger); }
|
|
1190
|
+
.u-crow.is-stale .u-crow-note.is-alert::before { color: var(--warn); }
|
|
1191
|
+
@media (prefers-reduced-motion: no-preference) { .u-crow { transition: background-color 0.15s var(--ease); } }
|
|
1106
1192
|
.u-rail-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); }
|
|
1107
1193
|
@media (max-width: 1199px) { .u-rail { display: none; } }
|
|
1108
1194
|
@media (max-width: 899px) {
|
|
@@ -1460,6 +1546,49 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1460
1546
|
.oc-tools { margin-left: 0; width: 100%; flex-wrap: wrap; row-gap: var(--s-2); }
|
|
1461
1547
|
.oc-mini { display: none; }
|
|
1462
1548
|
}
|
|
1549
|
+
/* ── Fleet → Org chart → Office (fleet-office.js): the fleet as a pixel office on one <canvas>. Its size is set through
|
|
1550
|
+
the CSSOM (never a style attribute, #1300); the stage scrolls, the page around it too. ── */
|
|
1551
|
+
.oo { display: flex; flex-direction: column; gap: var(--s-2); }
|
|
1552
|
+
.oo-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
|
|
1553
|
+
.oo-pick { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-2); }
|
|
1554
|
+
.oo-pick .tb-select { max-width: 200px; }
|
|
1555
|
+
.oo-people { position: relative; }
|
|
1556
|
+
/* #2399: the offices' minimal editor (fleet-office-editor.js) — one row per office, its members unfolded under it. */
|
|
1557
|
+
.oe-form { border: 0; margin: 0; padding: 0; min-width: 0; }
|
|
1558
|
+
/* #2431: after a 409, the two ways on (keep my edits, load theirs), side by side; they wrap on a phone. */
|
|
1559
|
+
.oe-conflict { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
|
|
1560
|
+
.oe-list { list-style: none; margin: var(--s-3) 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-3); }
|
|
1561
|
+
.oe-row { padding: var(--s-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); display: flex; flex-direction: column; gap: var(--s-2); }
|
|
1562
|
+
.oe-head { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-2); }
|
|
1563
|
+
.oe-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
1564
|
+
.oe-label { font-size: var(--fs-xs); color: var(--text-2); }
|
|
1565
|
+
.oe-name { min-width: 0; width: 16em; max-width: 100%; }
|
|
1566
|
+
.oe-remove { margin-left: auto; }
|
|
1567
|
+
.oe-members { display: flex; flex-wrap: wrap; gap: var(--s-3); }
|
|
1568
|
+
.oe-group { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; min-width: 12em; max-height: 240px; overflow-y: auto; }
|
|
1569
|
+
.oe-group legend { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-2); padding: 0; margin-bottom: 4px; }
|
|
1570
|
+
.oo-people > .btn.on { border-color: var(--accent); }
|
|
1571
|
+
.oo-people-pop { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; width: min(280px, calc(100vw - 32px)); max-height: 340px; overflow-y: auto;
|
|
1572
|
+
display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-2); }
|
|
1573
|
+
.oo-people-pop input[type="search"] { width: 100%; height: 32px; }
|
|
1574
|
+
.oo-people-list { list-style: none; margin: 0; padding: 0; }
|
|
1575
|
+
.oo-people-list label { display: flex; align-items: center; gap: 8px; padding: 3px 2px; font-size: var(--fs-xs); color: var(--text); cursor: pointer; overflow-wrap: anywhere; }
|
|
1576
|
+
.oo-note { font-size: var(--fs-xs); }
|
|
1577
|
+
.oo-stage { position: relative; height: clamp(420px, calc(100dvh - 280px), 960px); overflow: auto; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg); }
|
|
1578
|
+
.oo-canvas { display: block; margin: 0 auto; image-rendering: pixelated; }
|
|
1579
|
+
.oo-canvas.hot { cursor: pointer; }
|
|
1580
|
+
.oo-tip { position: absolute; left: 0; top: 0; z-index: 5; max-width: 240px; pointer-events: none; padding: 6px 10px; font-size: var(--fs-xs); line-height: 1.4;
|
|
1581
|
+
color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-2); overflow-wrap: anywhere; }
|
|
1582
|
+
/* The desks as a list: for a screen reader, and shown (over the stage's corner) while the keyboard is in it. */
|
|
1583
|
+
.oo-desks { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; list-style: none; }
|
|
1584
|
+
.oo-desks:focus-within { position: sticky; left: var(--s-2); bottom: var(--s-2); width: auto; height: auto; max-height: 40%; margin: 0; padding: var(--s-1); clip: auto; overflow-y: auto;
|
|
1585
|
+
display: inline-flex; flex-wrap: wrap; gap: 4px; max-width: calc(100% - 2 * var(--s-2)); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); white-space: normal; }
|
|
1586
|
+
.oo-desk { font-size: var(--fs-xs); padding: 2px 6px; border-radius: var(--r-sm); background: var(--bg); color: var(--text); border: 1px solid var(--border); }
|
|
1587
|
+
.oo-desk:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
|
|
1588
|
+
@media (max-width: 640px) {
|
|
1589
|
+
.oo-stage { height: calc(100dvh - 300px); min-height: 340px; }
|
|
1590
|
+
.oo-pick .tb-select { max-width: 150px; }
|
|
1591
|
+
}
|
|
1463
1592
|
/* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
|
|
1464
1593
|
@media (max-width: 479px) {
|
|
1465
1594
|
.p-fleet .seg-item:not(.active) > span, .p-settings .seg-item:not(.active) > span { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// What a guest's browser is shown for a page it cannot open. Loaded from /assets/guest-refused.js.
|
|
2
|
+
//
|
|
3
|
+
// The gate answers a guest's navigation with this page instead of its JSON (#2399 smoke): "members" for a page that is
|
|
4
|
+
// not a guest's (Fleet, Settings…), "ended" when no chat is shared with the guest any more. The page's words are in
|
|
5
|
+
// the browser's language, as the sign-in page's are; the HTML carries the English for a browser without scripts.
|
|
6
|
+
(() => {
|
|
7
|
+
"use strict";
|
|
8
|
+
const T = {
|
|
9
|
+
en: {
|
|
10
|
+
members_title: "This page is not open to guests",
|
|
11
|
+
members_lead: "This page is for the fleet's members. As a guest you can use the chats shared with you.",
|
|
12
|
+
members_go: "Open your chats",
|
|
13
|
+
ended_title: "No chats are shared with you",
|
|
14
|
+
ended_lead: "Your guest access on this fleet has no chat now — you may have left the room it came from. Ask whoever invited you to share a chat with you again.",
|
|
15
|
+
},
|
|
16
|
+
"zh-TW": {
|
|
17
|
+
members_title: "訪客無法開啟這個頁面",
|
|
18
|
+
members_lead: "這個頁面只給 fleet 的成員使用。身為訪客,你可以使用分享給你的聊天。",
|
|
19
|
+
members_go: "打開你的聊天",
|
|
20
|
+
ended_title: "目前沒有分享給你的聊天",
|
|
21
|
+
ended_lead: "你在這個 fleet 的訪客權限目前沒有任何聊天——你可能已經離開當初的頻道。請聯絡邀請你的人,再分享聊天給你。",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const lang = /^zh/i.test(navigator.language || "") ? "zh-TW" : "en";
|
|
25
|
+
const t = (k) => T[lang][k] || T.en[k] || k;
|
|
26
|
+
document.documentElement.lang = lang === "zh-TW" ? "zh-Hant" : "en";
|
|
27
|
+
const main = document.getElementById("refused");
|
|
28
|
+
const why = main && main.dataset.why === "ended" ? "ended" : "members";
|
|
29
|
+
document.getElementById("title").textContent = t(`${why}_title`);
|
|
30
|
+
document.getElementById("lead").textContent = t(`${why}_lead`);
|
|
31
|
+
// Nothing to open when no chat is shared (the chats would answer this page again): no button then.
|
|
32
|
+
const go = document.getElementById("go");
|
|
33
|
+
if (go) { if (why === "ended") go.remove(); else go.textContent = t("members_go"); }
|
|
34
|
+
document.title = `AgEnD — ${t(`${why}_title`)}`;
|
|
35
|
+
})();
|
|
@@ -9,7 +9,7 @@ import { html, useEffect, useState } from "./app-html.js";
|
|
|
9
9
|
import { t } from "./app-i18n.js";
|
|
10
10
|
import { createStore, useStore } from "./app-store.js";
|
|
11
11
|
import { Icon } from "./ui-icons.js";
|
|
12
|
-
import { UsageBody, UsageDialog } from "./usage-dialog.js";
|
|
12
|
+
import { UsageBody, UsageCompactToggle, UsageDialog } from "./usage-dialog.js";
|
|
13
13
|
|
|
14
14
|
export const TEXT_SIZE_KEY = "agend_text_size";
|
|
15
15
|
export const VIEW_FIT_KEY = "agend_view_fit";
|
|
@@ -126,7 +126,7 @@ export function UsageRail({ route }) {
|
|
|
126
126
|
const { open } = useStore(usageRailStore);
|
|
127
127
|
if (!wide || !open || !route || !RAIL_PANELS.includes(route.panel) || !usageOffered()) return null;
|
|
128
128
|
return html`<aside data-trust="usage" class="u-rail" aria-label=${t("view.usage.title_plain")}>
|
|
129
|
-
<div class="u-rail-head"><h2>${t("view.usage.title")}</h2
|
|
129
|
+
<div class="u-rail-head"><h2>${t("view.usage.title")}</h2><${UsageCompactToggle} />
|
|
130
130
|
<button type="button" class="icon-btn" onClick=${() => setUsageRail(false)} aria-label=${t("app.usageHide")} title=${t("app.usageHide")}><${Icon} name="close" size=${16} /></button></div>
|
|
131
131
|
<div class="u-rail-body"><${UsageBody} owner="usage-rail" /></div>
|
|
132
132
|
</aside>`;
|
|
@@ -82,11 +82,12 @@ export function backendLabel(backend) { return BACKEND_LABELS[backend] || backen
|
|
|
82
82
|
export const KNOWN_BACKENDS = new Set(Object.keys(BACKEND_LABELS));
|
|
83
83
|
export const STATUS_KEYS = { running: "statusRunning", paused: "statusPaused", stopped: "statusStopped", crashed: "statusCrashed" };
|
|
84
84
|
|
|
85
|
-
/** The status chips, in order
|
|
86
|
-
export const STATUS_FACETS = ["working", "needs", "idle", "stopped", "crashed"];
|
|
85
|
+
/** The status chips, in order: what the row's dot says, including agents queued for this boot. */
|
|
86
|
+
export const STATUS_FACETS = ["working", "needs", "starting", "idle", "stopped", "crashed"];
|
|
87
87
|
/** Which status chip a row falls under: needs you first, then crashed, not running, working (or stuck), idle. */
|
|
88
88
|
export function statusFacet(it, exec, awaiting) {
|
|
89
89
|
if (awaiting != null) return "needs";
|
|
90
|
+
if (it?.startup_phase === "queued" || it?.startup_phase === "starting") return "starting";
|
|
90
91
|
if (!it || it.status === "crashed") return "crashed";
|
|
91
92
|
if (it.status !== "running") return "stopped";
|
|
92
93
|
return exec === "working" || exec === "stuck" ? "working" : "idle";
|
|
@@ -127,7 +128,8 @@ export function filterSidebar(groupNames, instByGroup, rosterByName, query, labe
|
|
|
127
128
|
}
|
|
128
129
|
export function instanceTooltip(it) {
|
|
129
130
|
const context = it.context_pct != null && Number.isFinite(it.context_pct) ? `${Math.round(Math.min(100, Math.max(0, it.context_pct)))}%` : tn("tooltipUnavailable");
|
|
130
|
-
const status =
|
|
131
|
+
const status = it.startup_phase === "queued" ? t("app.statusQueued") : it.startup_phase === "starting" ? t("app.statusStarting")
|
|
132
|
+
: STATUS_KEYS[it.status] ? tn(STATUS_KEYS[it.status]) : it.status || tn("statusUnknown");
|
|
131
133
|
const line = (key, value) => tn(key, { value });
|
|
132
134
|
const lines = [];
|
|
133
135
|
const name = nameOf(it);
|
|
@@ -361,12 +363,15 @@ export function avatarOf(it) {
|
|
|
361
363
|
/** The status dot on the avatar: needs you, crashed, paused, stopped, looks stuck, working, idle — colour AND a label. */
|
|
362
364
|
export function dotState(it, exec, awaiting) {
|
|
363
365
|
if (awaiting != null) return "needs";
|
|
366
|
+
if (it?.startup_phase === "queued") return "queued";
|
|
367
|
+
if (it?.startup_phase === "starting") return "starting";
|
|
364
368
|
if (!it || it.status === "crashed") return "crashed";
|
|
365
369
|
if (it.status === "paused") return "paused";
|
|
366
370
|
if (it.status !== "running") return "stopped";
|
|
367
371
|
return exec === "stuck" ? "stuck" : exec === "working" ? "working" : "idle";
|
|
368
372
|
}
|
|
369
373
|
const DOT = {
|
|
374
|
+
queued: ["off", () => t("app.statusQueued")], starting: ["busy", () => t("app.statusStarting")],
|
|
370
375
|
needs: ["warn", () => t("app.needsYou")], crashed: ["bad", () => t("app.statusCrashed")], paused: ["off hollow", () => t("app.statusPaused")],
|
|
371
376
|
stopped: ["off", () => t("app.statusStopped")], stuck: ["warn", () => t("app.statusStuck")], working: ["busy", () => t("app.statusWorking")],
|
|
372
377
|
idle: ["ok", () => t("app.statusIdle")],
|
|
@@ -393,7 +398,7 @@ export function InstanceAvatar({ it, state, cli = false }) {
|
|
|
393
398
|
|
|
394
399
|
// ── The list ──
|
|
395
400
|
|
|
396
|
-
const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet === "working" ? t("app.statusWorking") : facet === "idle" ? t("app.statusIdle")
|
|
401
|
+
const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet === "starting" ? t("app.statusStarting") : facet === "working" ? t("app.statusWorking") : facet === "idle" ? t("app.statusIdle")
|
|
397
402
|
: facet === "crashed" ? t("app.statusCrashed") : t("app.statusStopped"));
|
|
398
403
|
|
|
399
404
|
// ── #2179: when each instance last replied to a person (the status frame's last_reply) ──
|
|
@@ -571,7 +576,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
571
576
|
const sel = f[key];
|
|
572
577
|
const summary = !open && sel.length ? (sel.length <= 2 ? sel.map(text).join(", ") : tn("facetSelected", { n: sel.length })) : "";
|
|
573
578
|
const hint = tn(open ? "facetHide" : "facetShow", { group: label });
|
|
574
|
-
return html`<div class=${`nav-facet${open ? " open" : ""}`}>
|
|
579
|
+
return html`<div class=${`nav-facet${open ? " open" : ""}${summary ? " has-sum" : ""}`}>
|
|
575
580
|
<button type="button" class="nav-facet-head" aria-expanded=${open ? "true" : "false"} aria-controls=${`nav-facet-${key}`}
|
|
576
581
|
title=${summary ? `${hint} (${label} · ${summary})` : hint} onClick=${() => toggleFacet(key)}>
|
|
577
582
|
<${Icon} name="chevron" size=${12} cls=${open ? "caret" : "caret folded"} /><span class="nav-facet-label">${label}</span>
|
|
@@ -614,6 +619,9 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
614
619
|
aria-label=${tn(starred ? "favRemove" : "favAdd", { name: label })} title=${full ? tn("favFull", { max: MAX_FAVORITES }) : tn(starred ? "favRemove" : "favAdd", { name: label })}
|
|
615
620
|
onClick=${() => toggleFavorite(name, known)}><${Icon} name="star" size=${14} /></button>` : null}</li>`;
|
|
616
621
|
};
|
|
622
|
+
// #2401: an open list of two or more lines leaves room under the count for "shown" and Clear (stacked); otherwise compact.
|
|
623
|
+
const facetSet = f.status.length > 0 || f.cli.length > 0;
|
|
624
|
+
const stacked = (!viewOnly && facetOpen.status) || (facetOpen.cli && backends.length > 1);
|
|
617
625
|
// The list scrolls; the filter under it stays in view (#999), so it sits outside the scrolling section.
|
|
618
626
|
return html`<div class=${`view-roster inst-nav${editing && canStar ? " fav-editing" : ""}`}>
|
|
619
627
|
<div data-trust="navigation" class="side-section" id="instanceList" ref=${list}>
|
|
@@ -653,13 +661,22 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
653
661
|
${f.q ? html`<button type="button" class="icon-btn v-filter-clear" aria-label=${tn("filterClear")} title=${tn("filterClear")}
|
|
654
662
|
onClick=${() => { recordEdit(filterRef.current); setNavFilter({ q: "" }); filterRef.current && filterRef.current.focus(); }}><${Icon} name="close" size=${14} /></button>` : null}
|
|
655
663
|
</label>
|
|
656
|
-
<div class
|
|
664
|
+
<div class=${`nav-facets${stacked ? "" : " compact"}`}>
|
|
657
665
|
<div class="nav-facet-groups">
|
|
658
666
|
${viewOnly ? null : chips("status", STATUS_FACETS, tn("filterStatus"), statusText)}
|
|
659
667
|
${backends.length > 1 || f.cli.length ? chips("cli", backends, tn("filterCli"), backendLabel) : null}
|
|
660
|
-
${f.status.length || f.cli.length ? html`<button type="button" class="chip-btn nav-facet-reset" onClick=${() => setNavFilter({ status: [], cli: [] })}>${tn("filterFacetsReset")}</button>` : null}
|
|
661
668
|
</div>
|
|
662
|
-
|
|
669
|
+
${/* #2401: the count, its "shown" and Clear stack at the row's right end — beside an open list there is room under
|
|
670
|
+
the count, so setting a facet adds no row (a word beside the count, or Clear beside it, wrapped CLI under the
|
|
671
|
+
list). With no such list (.compact) they share one cell of one line: a facet set, Clear takes the count's place
|
|
672
|
+
(the count kept for a screen reader and in Clear's title). Either way the column keeps Clear's width (idle, there
|
|
673
|
+
unseen at no height: .is-idle, a class — never a style attribute, #1300), and stacked the word's too
|
|
674
|
+
(data-reserve, drawn unseen at no height), so the headers' box beside it never narrows. */ ""}
|
|
675
|
+
<div class="nav-facet-end" data-reserve=${tn("filterShown")}>
|
|
676
|
+
<p class=${`note nav-shown${facetSet ? " is-set" : ""}`} aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? html`<span class="nav-shown-word"> ${tn("filterShown")}</span>` : ""}</p>
|
|
677
|
+
<button type="button" class=${`chip-btn nav-facet-reset${facetSet ? "" : " is-idle"}`} title=${facetSet ? `${shown.shown} / ${shown.total} ${tn("filterShown")}` : undefined}
|
|
678
|
+
onClick=${() => setNavFilter({ status: [], cli: [] })}>${tn("filterFacetsReset")}</button>
|
|
679
|
+
</div>
|
|
663
680
|
</div>
|
|
664
681
|
</div>`}
|
|
665
682
|
</div>`;
|
|
@@ -10,7 +10,7 @@ import { t } from "./app-i18n.js";
|
|
|
10
10
|
* <${Dialog} title="Create task" onClose=${…} actions=${html`…`}>…body…</${Dialog}>
|
|
11
11
|
* Rendered = open; the parent unmounts it to close. `busy` keeps Esc and the backdrop from closing it mid-request.
|
|
12
12
|
*/
|
|
13
|
-
export function Dialog({ title, onClose, actions, children, busy = false, wide = false, drawer = false, label }) {
|
|
13
|
+
export function Dialog({ title, onClose, actions, children, busy = false, wide = false, drawer = false, label, tools = null }) {
|
|
14
14
|
const ref = useRef(null);
|
|
15
15
|
const opener = useRef(null);
|
|
16
16
|
const busyRef = useRef(busy);
|
|
@@ -38,7 +38,7 @@ export function Dialog({ title, onClose, actions, children, busy = false, wide =
|
|
|
38
38
|
}, [onClose]);
|
|
39
39
|
return html`<dialog data-trust="dialog" ref=${ref} class=${`dlg${wide ? " dlg-wide" : ""}${drawer ? " dlg-drawer" : ""}`} aria-label=${label || title}>
|
|
40
40
|
<div class="dlg-box">
|
|
41
|
-
<div class="dlg-head"><h2 class="dlg-title">${title}</h2
|
|
41
|
+
<div class="dlg-head"><h2 class="dlg-title">${title}</h2>${tools ? html`<span class="dlg-tools">${tools}</span>` : null}
|
|
42
42
|
<button type="button" class="icon-btn dlg-x" aria-label=${t("app.close")} title=${t("app.close")} onClick=${() => { if (!busyRef.current) onClose(); }}><${Icon} name="close" /></button></div>
|
|
43
43
|
<div class="dlg-body">${children}</div>
|
|
44
44
|
${actions ? html`<div class="dlg-actions">${actions}</div>` : null}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "./app-html.js";
|
|
5
5
|
import { t } from "./app-i18n.js";
|
|
6
6
|
import { useLease } from "./app-ctx.js";
|
|
7
|
-
import { guestPage } from "./app-store.js";
|
|
7
|
+
import { createStore, guestPage, useStore } from "./app-store.js";
|
|
8
|
+
import { onPrefsChanged, prefInForce, savePref } from "./app-prefs.js";
|
|
8
9
|
import { readStream } from "./read-stream.js";
|
|
9
10
|
import { Dialog } from "./ui-dialog.js";
|
|
10
11
|
import { ErrorState, Skeleton } from "./ui-states.js";
|
|
@@ -129,6 +130,142 @@ export function updatedAgo(fetchedAt, now = Date.now()) {
|
|
|
129
130
|
return tn("usageUpdatedAgo", { age: min < 60 ? tn("usageMinutes", { n: min }) : tn("usageHours", { n: Math.floor(min / 60) }) });
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
// ── Compact: one line per login (a synced preference, "usage.compact"; the dialog and the rail alike) ──
|
|
134
|
+
const COMPACT_KEY = "agend_usage_compact";
|
|
135
|
+
function readCompact() {
|
|
136
|
+
const p = prefInForce("usage.compact");
|
|
137
|
+
return (p ? p.value : stored(COMPACT_KEY) === "on" ? true : undefined) === true;
|
|
138
|
+
}
|
|
139
|
+
export const usageViewStore = createStore({ compact: readCompact() });
|
|
140
|
+
export function setUsageCompact(on) {
|
|
141
|
+
usageViewStore.set({ compact: !!on });
|
|
142
|
+
savePref("usage.compact", on ? true : null); // the full cards are the default: switching back clears it
|
|
143
|
+
}
|
|
144
|
+
onPrefsChanged((keys) => { if (keys.has("usage.compact")) usageViewStore.set({ compact: readCompact() }); });
|
|
145
|
+
/** The header's switch between one line per login and the full cards (beside the title: the dialog's and the rail's). */
|
|
146
|
+
export function UsageCompactToggle() {
|
|
147
|
+
const { compact } = useStore(usageViewStore);
|
|
148
|
+
const [label, title] = compact ? [tn("usageFull"), tn("usageFullTitle")] : [tn("usageCompact"), tn("usageCompactTitle")];
|
|
149
|
+
return html`<button type="button" class="btn btn-ghost btn-sm u-compact-toggle" aria-pressed=${compact ? "true" : "false"} title=${title}
|
|
150
|
+
onClick=${() => setUsageCompact(!compact)}>${label}</button>`;
|
|
151
|
+
}
|
|
152
|
+
const TWO_DAYS = 48 * 3_600_000;
|
|
153
|
+
/**
|
|
154
|
+
* A login's two columns on its compact line: `short`, its shortest percent window under two days (the session), and
|
|
155
|
+
* `long`, its longest of two days or more (the week, the month) — null when it has none (a dash in that column). A window
|
|
156
|
+
* that reset since the numbers were read is left out (its full card shows no number); one of unknown length is a long one.
|
|
157
|
+
*/
|
|
158
|
+
export function compactSlots(p) {
|
|
159
|
+
const pct = ((p && p.metrics) || []).filter((m) => m && m.type === "percent" && !m.resetPassed);
|
|
160
|
+
const isShort = (m) => Number(m.windowMs) > 0 && m.windowMs < TWO_DAYS;
|
|
161
|
+
const short = pct.filter(isShort).sort((a, b) => a.windowMs - b.windowMs)[0] || null;
|
|
162
|
+
const long = pct.filter((m) => !isShort(m)).map((m, i) => ({ m, i }))
|
|
163
|
+
.sort((a, b) => ((Number(b.m.windowMs) || -1) - (Number(a.m.windowMs) || -1)) || a.i - b.i)[0]?.m || null;
|
|
164
|
+
return { short, long };
|
|
165
|
+
}
|
|
166
|
+
/** The windows a compact line shows, short first. */
|
|
167
|
+
export const compactMetrics = (p) => { const { short, long } = compactSlots(p); return [short, long].filter(Boolean); };
|
|
168
|
+
/** A window as a few characters ("5h", "7d"); one whose length is not known says its label. */
|
|
169
|
+
export function windowTag(m) {
|
|
170
|
+
const ms = Number(m && m.windowMs);
|
|
171
|
+
if (!(ms > 0)) return usageText(m.label, m.labelI18n);
|
|
172
|
+
const h = ms / 3_600_000;
|
|
173
|
+
return h < 48 ? tn("usageWindowHours", { n: Math.round(h) }) : tn("usageWindowDays", { n: Math.round(h / 24) });
|
|
174
|
+
}
|
|
175
|
+
/** A time ahead in one unit, for the narrow column: "45m", "3h", "4d" (rounded down past an hour; the tooltip is exact). */
|
|
176
|
+
function coarseDuration(ms) {
|
|
177
|
+
const min = Math.max(1, Math.ceil(ms / 60_000));
|
|
178
|
+
if (min < 60) return tn("usageWindowMinutes", { n: min });
|
|
179
|
+
const h = Math.floor(min / 60);
|
|
180
|
+
return h < 48 ? tn("usageWindowHours", { n: h }) : tn("usageWindowDays", { n: Math.floor(h / 24) });
|
|
181
|
+
}
|
|
182
|
+
/** The soonest reset still ahead among `metrics`: { short: "↻ 2h", full: "resets in 2h 5m" }, or null when none is known. */
|
|
183
|
+
export function soonestReset(metrics, now = Date.now()) {
|
|
184
|
+
const at = metrics.map((m) => new Date(m.resetsAt || "").getTime()).filter((t) => Number.isFinite(t) && t > now).sort((a, b) => a - b)[0];
|
|
185
|
+
return at === undefined ? null : { short: `↻ ${coarseDuration(at - now)}`, full: resetText(new Date(at).toISOString()) };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The logins grouped by provider for the compact view (nine logins, five of them Claude's: the provider once, then a
|
|
189
|
+
* line per login), in the order the person set (↑↓): a group where its first login is, its logins in their order.
|
|
190
|
+
*/
|
|
191
|
+
export function compactGroups(providers) {
|
|
192
|
+
const groups = new Map();
|
|
193
|
+
providers.forEach((p, i) => {
|
|
194
|
+
const base = providerTitle(p).base;
|
|
195
|
+
if (!groups.has(base)) groups.set(base, { base, items: [] });
|
|
196
|
+
groups.get(base).items.push({ p, i });
|
|
197
|
+
});
|
|
198
|
+
return [...groups.values()];
|
|
199
|
+
}
|
|
200
|
+
const level = (pct) => (pct >= 90 ? "crit" : pct >= 70 ? "warn" : "");
|
|
201
|
+
/**
|
|
202
|
+
* #2240: an account whose percent windows come from several pools (agy: Gemini, Claude & others, Gemini 4 Argon) — each
|
|
203
|
+
* pool with its windows and the tighter one (the most used); null for an account of one pool (its 5h / 7d lines).
|
|
204
|
+
*/
|
|
205
|
+
export function compactPools(p) {
|
|
206
|
+
const pct = ((p && p.metrics) || []).filter((m) => m && m.type === "percent" && !m.resetPassed);
|
|
207
|
+
const pools = new Map();
|
|
208
|
+
for (const m of pct) {
|
|
209
|
+
if (!m.pool || typeof m.pool.label !== "string") return null;
|
|
210
|
+
if (!pools.has(m.pool.label)) pools.set(m.pool.label, { pool: m.pool, metrics: [] });
|
|
211
|
+
pools.get(m.pool.label).metrics.push(m);
|
|
212
|
+
}
|
|
213
|
+
if (pools.size < 2) return null;
|
|
214
|
+
return [...pools.values()].map((x) => ({ ...x, worst: x.metrics.reduce((a, b) => ((b.used ?? 0) > (a.used ?? 0) ? b : a)) }));
|
|
215
|
+
}
|
|
216
|
+
const pctOf = (m) => Math.min(100, Math.max(0, m.used ?? 0));
|
|
217
|
+
/**
|
|
218
|
+
* One window on its own line: its few characters ("5h"), a full-width bar and the percent, coloured as the full card.
|
|
219
|
+
* A pool's line (#2240): the pool's name in place of the window, its every window in the tooltip.
|
|
220
|
+
*/
|
|
221
|
+
function CompactMeter({ m, tag, title }) {
|
|
222
|
+
const bar = useRef(null);
|
|
223
|
+
const pct = Math.min(100, Math.max(0, m.used ?? 0)), cls = level(pct);
|
|
224
|
+
useLayoutEffect(() => { if (bar.current) bar.current.style.width = `${pct}%`; }, [pct]); // CSSOM (#1300)
|
|
225
|
+
const word = cls === "crit" ? tn("usageNear") : cls === "warn" ? tn("usageHigh") : "";
|
|
226
|
+
return html`<span class="u-cm" title=${title || `${usageText(m.label, m.labelI18n)}: ${pct.toFixed(0)}%${word ? ` (${word})` : ""}`}>
|
|
227
|
+
<span class="u-cm-tag">${tag || windowTag(m)}</span><span class="u-cm-bar"><span ref=${bar} class=${`u-cm-fill ${cls}`}></span></span>
|
|
228
|
+
<span class=${`u-cm-pct ${cls}`}>${pct.toFixed(0)}%</span></span>`;
|
|
229
|
+
}
|
|
230
|
+
/** What a login says instead of numbers (not logged in, an error), or "" when it has numbers. */
|
|
231
|
+
function rowNote(p) {
|
|
232
|
+
if (p.status === "error") return usageText(p.error || tn("usage.error_fallback"), p.errorI18n);
|
|
233
|
+
if (p.status === "no-credentials") return tn("usage.not_logged_in");
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A login in three short lines, under its provider's name (the user's pick, 2026-10-22): its name and plan with the
|
|
238
|
+
* soonest reset on the right; then its short window and its long one, each a labelled full-width bar and the percent.
|
|
239
|
+
* Numbers not current, a failed read or no login: one muted line in place of the bars, what is wrong as its tooltip.
|
|
240
|
+
* Pressed, it opens its full card.
|
|
241
|
+
*/
|
|
242
|
+
export function CompactRow({ p, onExpand }) {
|
|
243
|
+
const title = providerTitle(p), failed = p.status === "error" || p.status === "no-credentials";
|
|
244
|
+
const pools = failed || p.stale ? null : compactPools(p);
|
|
245
|
+
const metrics = failed || p.stale ? [] : pools ? pools.flatMap((x) => x.metrics) : compactMetrics(p);
|
|
246
|
+
const note = rowNote(p), stale = p.stale ? staleNote(p) : null;
|
|
247
|
+
const say = stale ? stale.mark : note || tn("usage.no_data");
|
|
248
|
+
const detail = stale ? [stale.mark, stale.detail].filter(Boolean).join(" — ") : say;
|
|
249
|
+
const reset = soonestReset(metrics);
|
|
250
|
+
const name = title.profile || title.base;
|
|
251
|
+
return html`<button type="button" class=${`u-crow${p.stale ? " is-stale" : ""}${pools ? " is-pooled" : ""}`} aria-expanded="false" title=${`${p.name} — ${tn("usageExpand")}`} onClick=${onExpand}>
|
|
252
|
+
<span class="u-crow-head"><span class="u-crow-name">${name}</span>
|
|
253
|
+
${p.plan ? html`<span class="pill u-plan" title=${p.plan}><span aria-hidden="true">${planBadge(p.plan)}</span><span class="sr-only">${p.plan}</span></span>` : null}
|
|
254
|
+
${reset ? html`<span class="u-crow-reset" title=${reset.full}>${reset.short}<span class="sr-only"> (${reset.full})</span></span>` : null}</span>
|
|
255
|
+
${pools ? pools.map((x) => html`<${CompactMeter} key=${x.pool.label} m=${x.worst} tag=${usageText(x.pool.label, x.pool.labelI18n)}
|
|
256
|
+
title=${x.metrics.map((m) => `${usageText(m.label, m.labelI18n)}: ${pctOf(m).toFixed(0)}%`).join(" · ")} />`)
|
|
257
|
+
: metrics.length ? metrics.map((m, j) => html`<${CompactMeter} key=${j} m=${m} />`)
|
|
258
|
+
: html`<span class=${`u-crow-note${(stale || (p.status === "error" && !p.transient)) ? " is-alert" : ""}`} title=${detail}>${say}</span>`}
|
|
259
|
+
</button>`;
|
|
260
|
+
}
|
|
261
|
+
/** A provider's logins: its name once (×N), in small capitals, then each login (or the full card of one opened). */
|
|
262
|
+
export function CompactGroup({ group, line }) {
|
|
263
|
+
return html`<section class="u-cgroup">
|
|
264
|
+
<div class="u-cg-head"><span class="u-cg-name" title=${group.base}>${group.base}</span>${group.items.length > 1 ? html`<span class="u-cg-count">×${group.items.length}</span>` : null}</div>
|
|
265
|
+
${group.items.map(line)}
|
|
266
|
+
</section>`;
|
|
267
|
+
}
|
|
268
|
+
|
|
132
269
|
/**
|
|
133
270
|
* The usage read and its rows: the dialog's body and the rail's (#1673). `owner`: its lease's key — the reads and the
|
|
134
271
|
* minute's timer live and end with it.
|
|
@@ -141,6 +278,11 @@ export function UsageBody({ owner }) {
|
|
|
141
278
|
const [failed, setFailed] = useState(false);
|
|
142
279
|
const [order, setOrder] = useState(() => { try { const a = JSON.parse(stored(USAGE_ORDER_KEY) || "[]"); return Array.isArray(a) ? a.filter((x) => typeof x === "string") : []; } catch { return []; } });
|
|
143
280
|
const reads = useMemo(() => readStream(lease), [lease]);
|
|
281
|
+
// Compact (one line per login): a line pressed opens its full card here, until folded or the mode changes.
|
|
282
|
+
const { compact } = useStore(usageViewStore);
|
|
283
|
+
const [expanded, setExpanded] = useState(() => new Set());
|
|
284
|
+
useEffect(() => { setExpanded(new Set()); }, [compact]);
|
|
285
|
+
const toggleOne = (id) => setExpanded((was) => { const next = new Set(was); if (next.has(id)) next.delete(id); else next.add(id); return next; });
|
|
144
286
|
// Refresh (force) supersedes a read on its way; the minute's tick waits for it.
|
|
145
287
|
const load = async (force) => {
|
|
146
288
|
const token = reads.begin(force);
|
|
@@ -188,10 +330,10 @@ export function UsageBody({ owner }) {
|
|
|
188
330
|
else if (!data || !Array.isArray(data.providers)) content = html`<${ErrorState} message=${tn("usageFail")} onRetry=${() => load(true)} />`;
|
|
189
331
|
else {
|
|
190
332
|
const providers = ordered(data.providers);
|
|
191
|
-
|
|
192
|
-
${providers.map((p, i) => html`<section key=${key(p)} class=${p.stale ? "u-provider is-stale" : "u-provider"}>
|
|
333
|
+
const card = (p, i) => html`<section key=${key(p)} class=${p.stale ? "u-provider is-stale" : "u-provider"}>
|
|
193
334
|
<div class="u-head"><strong title=${p.name}>${providerTitle(p).base}</strong>${p.plan ? html`<span class="pill u-plan" title=${p.plan}><span aria-hidden="true">${planBadge(p.plan)}</span><span class="sr-only">${p.plan}</span></span>` : null}
|
|
194
|
-
<span class="u-order"
|
|
335
|
+
<span class="u-order">${compact ? html`<button type="button" class="icon-btn u-collapse" aria-expanded="true" aria-label=${tn("usageCollapse")} title=${tn("usageCollapse")}
|
|
336
|
+
onClick=${() => toggleOne(key(p))}><${Icon} name="minus" size=${14} /></button>` : null}
|
|
195
337
|
<button type="button" class="icon-btn" disabled=${i === 0} aria-label=${tn("usageMoveUp")} title=${tn("usageMoveUp")} onClick=${() => move(key(p), -1, data.providers)}><${Icon} name="up" size=${14} /></button>
|
|
196
338
|
<button type="button" class="icon-btn" disabled=${i === providers.length - 1} aria-label=${tn("usageMoveDown")} title=${tn("usageMoveDown")} onClick=${() => move(key(p), 1, data.providers)}><${Icon} name="down" size=${14} /></button>
|
|
197
339
|
</span></div>
|
|
@@ -202,7 +344,11 @@ export function UsageBody({ owner }) {
|
|
|
202
344
|
: p.status === "no-credentials" ? html`<p class="note">${tn("usage.not_logged_in")}</p>${p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : null}`
|
|
203
345
|
: html`${(p.metrics || []).map((m, j) => html`<${UsageMetric} key=${j} m=${m} />`)}
|
|
204
346
|
${!p.stale && p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : !(p.metrics || []).length ? html`<p class="note">${tn("usage.no_data")}</p>` : null}`}
|
|
205
|
-
</section
|
|
347
|
+
</section>`;
|
|
348
|
+
content = html`${!providers.length ? html`<p class="note">${tn("usage.empty_active")}</p>` : null}
|
|
349
|
+
${compact ? compactGroups(providers).map((g) => html`<${CompactGroup} key=${g.base} group=${g}
|
|
350
|
+
line=${({ p, i }) => (expanded.has(key(p)) ? card(p, i) : html`<${CompactRow} key=${key(p)} p=${p} onExpand=${() => toggleOne(key(p))} />`)} />`)
|
|
351
|
+
: providers.map((p, i) => card(p, i))}
|
|
206
352
|
<div class="u-foot"><span class="note" title=${new Date(data.fetchedAt).toLocaleString()}>${updatedAgo(data.fetchedAt)}${failed ? html` · <span class="u-refresh-failed" role="status">${tn("usageRefreshFailed")}</span>` : data.refreshing ? html` · <span class="u-refreshing">${tn("usageRefreshing")}</span>` : null}</span>
|
|
207
353
|
${byAgent() ? html`<a class="btn btn-ghost btn-sm u-by-agent" href="/ui/fleet/usage" title=${tn("usageByAgentTitle")}>${tn("usageByAgent")}</a>` : null}
|
|
208
354
|
<button type="button" class="btn btn-sm" onClick=${() => load(true)}>${tn("usageRefresh")}</button></div>`;
|
|
@@ -218,5 +364,5 @@ const byAgent = () => !guestPage() && typeof location !== "undefined" && locatio
|
|
|
218
364
|
|
|
219
365
|
/** `owner`: the navigation that opened it — its reads and the minute's timer live and end with it. */
|
|
220
366
|
export function UsageDialog({ onClose, owner = "" }) {
|
|
221
|
-
return html`<${Dialog} title=${tn("usage.title")} onClose=${onClose} wide=${true}><${UsageBody} owner=${`usage-dialog:${owner}`} /></${Dialog}>`;
|
|
367
|
+
return html`<${Dialog} title=${tn("usage.title")} tools=${html`<${UsageCompactToggle} />`} onClose=${onClose} wide=${true}><${UsageBody} owner=${`usage-dialog:${owner}`} /></${Dialog}>`;
|
|
222
368
|
}
|
|
@@ -47,6 +47,16 @@ register("view", {
|
|
|
47
47
|
"usageNear": "near limit",
|
|
48
48
|
"usageMoveUp": "Move up",
|
|
49
49
|
"usageMoveDown": "Move down",
|
|
50
|
+
"usageCompact": "Compact",
|
|
51
|
+
"usageCompactTitle": "One line per login",
|
|
52
|
+
"usageFull": "Full",
|
|
53
|
+
"usageFullTitle": "The full card for each login",
|
|
54
|
+
"usageExpand": "Show the full card",
|
|
55
|
+
"usageCollapse": "Back to one line",
|
|
56
|
+
"usageWindowHours": "{n}h",
|
|
57
|
+
"usageWindowDays": "{n}d",
|
|
58
|
+
"usageWindowMinutes": "{n}m",
|
|
59
|
+
"usageStaleMark": "Not current",
|
|
50
60
|
"tooltipBackend": "Backend: {value}",
|
|
51
61
|
"tooltipStatus": "Status: {value}",
|
|
52
62
|
"tooltipContext": "Context: {value}",
|
|
@@ -279,6 +289,16 @@ register("view", {
|
|
|
279
289
|
"usageNear": "接近上限",
|
|
280
290
|
"usageMoveUp": "上移",
|
|
281
291
|
"usageMoveDown": "下移",
|
|
292
|
+
"usageCompact": "精簡",
|
|
293
|
+
"usageCompactTitle": "每個登入一行",
|
|
294
|
+
"usageFull": "完整",
|
|
295
|
+
"usageFullTitle": "每個登入顯示完整卡片",
|
|
296
|
+
"usageExpand": "顯示完整卡片",
|
|
297
|
+
"usageCollapse": "收回成一行",
|
|
298
|
+
"usageWindowHours": "{n}小時",
|
|
299
|
+
"usageWindowDays": "{n}天",
|
|
300
|
+
"usageWindowMinutes": "{n}分",
|
|
301
|
+
"usageStaleMark": "不是最新",
|
|
282
302
|
"tooltipBackend": "後端:{value}",
|
|
283
303
|
"tooltipStatus": "狀態:{value}",
|
|
284
304
|
"tooltipContext": "上下文:{value}",
|