@app-vitals/agent-admin 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/Dockerfile +43 -0
  2. package/README.md +72 -2
  3. package/e2e/agents-page.e2e.ts +338 -0
  4. package/e2e/chat-degraded.e2e.ts +153 -0
  5. package/e2e/chat-page.e2e.ts +169 -0
  6. package/e2e/chat-progress.e2e.ts +206 -0
  7. package/e2e/chat-thread-page.e2e.ts +444 -0
  8. package/e2e/login-page.e2e.ts +148 -0
  9. package/e2e/test-server.ts +427 -0
  10. package/openapi.json +6098 -0
  11. package/openapi.platform.json +5553 -0
  12. package/package.json +62 -4
  13. package/playwright.config.ts +25 -0
  14. package/prisma/client/browser.ts +134 -0
  15. package/prisma/client/client.ts +158 -0
  16. package/prisma/client/commonInputTypes.ts +578 -0
  17. package/prisma/client/enums.ts +15 -0
  18. package/prisma/client/internal/class.ts +424 -0
  19. package/prisma/client/internal/prismaNamespace.ts +2898 -0
  20. package/prisma/client/internal/prismaNamespaceBrowser.ts +476 -0
  21. package/prisma/client/models/Account.ts +1774 -0
  22. package/prisma/client/models/AccountInvite.ts +1386 -0
  23. package/prisma/client/models/AccountMember.ts +1351 -0
  24. package/prisma/client/models/Agent.ts +4554 -0
  25. package/prisma/client/models/AgentAsk.ts +1373 -0
  26. package/prisma/client/models/AgentChatTokenUsageDailyByModel.ts +1638 -0
  27. package/prisma/client/models/AgentCronJob.ts +2416 -0
  28. package/prisma/client/models/AgentCronRun.ts +2858 -0
  29. package/prisma/client/models/AgentCronRunModelBreakdown.ts +1532 -0
  30. package/prisma/client/models/AgentCronRunSkillUsage.ts +1642 -0
  31. package/prisma/client/models/AgentEnv.ts +1421 -0
  32. package/prisma/client/models/AgentGitHubInstallationsSnapshot.ts +1289 -0
  33. package/prisma/client/models/AgentMember.ts +1316 -0
  34. package/prisma/client/models/AgentPhaseMethodology.ts +1355 -0
  35. package/prisma/client/models/AgentPlugin.ts +1421 -0
  36. package/prisma/client/models/AgentToken.ts +1380 -0
  37. package/prisma/client/models/AgentTool.ts +1351 -0
  38. package/prisma/client/models/AgentWorkQueueSnapshot.ts +1297 -0
  39. package/prisma/client/models/ChatThreadWatch.ts +1183 -0
  40. package/prisma/client/models/PushSubscription.ts +1233 -0
  41. package/prisma/client/models/SessionAlertState.ts +1187 -0
  42. package/prisma/client/models/SessionFollow.ts +1187 -0
  43. package/prisma/client/models/UserNotificationPrefs.ts +1221 -0
  44. package/prisma/client/models.ts +34 -0
  45. package/prisma/migrations/20260608000000_init/migration.sql +112 -0
  46. package/prisma/migrations/20260616000000_agent_members/migration.sql +18 -0
  47. package/prisma/migrations/20260624000000_add_agent_self_hosted/migration.sql +2 -0
  48. package/prisma/migrations/20260624120000_add_agent_repos/migration.sql +3 -0
  49. package/prisma/migrations/20260625000000_add_agent_cron_run/migration.sql +26 -0
  50. package/prisma/migrations/20260626000000_add_cron_run_token_fields/migration.sql +10 -0
  51. package/prisma/migrations/20260627000000_add_agent_chat_token_usage_daily/migration.sql +18 -0
  52. package/prisma/migrations/20260627010000_add_agent_chat_token_usage_fk/migration.sql +2 -0
  53. package/prisma/migrations/20260701000000_add_chat_threads_messages/migration.sql +37 -0
  54. package/prisma/migrations/20260701010000_drop_chat_threads_messages/migration.sql +17 -0
  55. package/prisma/migrations/20260701020000_add_cron_run_model_breakdown/migration.sql +23 -0
  56. package/prisma/migrations/20260701020000_chat_token_by_model/migration.sql +25 -0
  57. package/prisma/migrations/20260701030000_add_secret_flag_to_agent_env/migration.sql +2 -0
  58. package/prisma/migrations/20260701030000_drop_cron_run_legacy_columns/migration.sql +5 -0
  59. package/prisma/migrations/20260706000000_consolidate_cron_run_token_accounting/migration.sql +54 -0
  60. package/prisma/migrations/20260709000000_add_phase_to_agent_cron_run/migration.sql +8 -0
  61. package/prisma/migrations/20260717000000_add_item_tracking_to_agent_cron_run/migration.sql +10 -0
  62. package/prisma/migrations/20260717010000_add_agent_work_queue_snapshot/migration.sql +20 -0
  63. package/prisma/migrations/20260718000000_add_cron_phase_columns/migration.sql +25 -0
  64. package/prisma/migrations/20260718100000_drop_agent_cron_run_phase_column/migration.sql +8 -0
  65. package/prisma/migrations/20260724000000_add_agent_type_name/migration.sql +3 -0
  66. package/prisma/migrations/20260725232433_add_agent_author_allowlist/migration.sql +2 -0
  67. package/prisma/migrations/20260727120000_add_not_null_to_agent_author_allowlist/migration.sql +3 -0
  68. package/prisma/migrations/20260731000000_add_session_id_to_agent_cron_run/migration.sql +2 -0
  69. package/prisma/migrations/20260821000000_add_agent_restrict_slack_to_members/migration.sql +3 -0
  70. package/prisma/migrations/20260901000000_add_push_subscription_and_chat_thread_watch/migration.sql +40 -0
  71. package/prisma/migrations/20260903000000_add_agent_patch_author_allowlist/migration.sql +2 -0
  72. package/prisma/migrations/20260903120000_add_agent_review_author_allowlist/migration.sql +18 -0
  73. package/prisma/migrations/20260903150000_drop_agent_author_allowlist/migration.sql +14 -0
  74. package/prisma/migrations/20260905000000_add_started_at_index_to_agent_cron_run/migration.sql +11 -0
  75. package/prisma/migrations/20260906000000_add_session_follow_notification_prefs_alert_state/migration.sql +47 -0
  76. package/prisma/migrations/20260911000000_backfill_auto_follow_since/migration.sql +16 -0
  77. package/prisma/migrations/20260923115814_add_last_heartbeat_at_to_agent_cron_run/migration.sql +2 -0
  78. package/prisma/migrations/20260924120000_add_trial_expiry_fields_to_agent/migration.sql +6 -0
  79. package/prisma/migrations/20260929000000_add_agent_phase_methodology/migration.sql +21 -0
  80. package/prisma/migrations/20260929120000_add_agent_policy_fields/migration.sql +15 -0
  81. package/prisma/migrations/20261005000000_add_agent_github_installations_snapshot/migration.sql +21 -0
  82. package/prisma/migrations/20261008000000_add_cron_run_telemetry/migration.sql +50 -0
  83. package/prisma/migrations/20261008010000_add_account_models/migration.sql +48 -0
  84. package/prisma/migrations/20261008020000_add_cron_lockdown_disabled_at/migration.sql +3 -0
  85. package/prisma/migrations/20261009000000_chat_token_usage_bigint/migration.sql +7 -0
  86. package/prisma/migrations/20261010000000_add_cron_run_command_variant/migration.sql +2 -0
  87. package/prisma/migrations/20261019000000_add_agent_anthropic_identity/migration.sql +9 -0
  88. package/prisma/migrations/20261025000000_add_agent_ask/migration.sql +23 -0
  89. package/prisma/migrations/migration_lock.toml +3 -0
  90. package/prisma/schema/coding.prisma +85 -0
  91. package/prisma/schema/schema.prisma +506 -0
  92. package/prisma.config.ts +34 -0
  93. package/pwa-assets/icons/apple-touch-icon.png +0 -0
  94. package/pwa-assets/icons/favicon-32.png +0 -0
  95. package/pwa-assets/icons/icon-192.png +0 -0
  96. package/pwa-assets/icons/icon-512.png +0 -0
  97. package/pwa-assets/icons/icon-maskable-192.png +0 -0
  98. package/pwa-assets/icons/icon-maskable-512.png +0 -0
  99. package/src/account-agent-create.ts +42 -0
  100. package/src/account-agent-create.unit.test.ts +28 -0
  101. package/src/account-created-notifier.ts +71 -0
  102. package/src/account-created-notifier.unit.test.ts +70 -0
  103. package/src/account-email.ts +9 -0
  104. package/src/account-email.unit.test.ts +21 -0
  105. package/src/account-invites.ts +70 -0
  106. package/src/account-lifecycle.integration.test.ts +329 -0
  107. package/src/account-lifecycle.ts +206 -0
  108. package/src/account-lifecycle.unit.test.ts +108 -0
  109. package/src/account-members.ts +128 -0
  110. package/src/account-onboarding.integration.test.ts +101 -0
  111. package/src/account-onboarding.ts +95 -0
  112. package/src/account-trial-expiry-sweeper.ts +85 -0
  113. package/src/account-trial-warning-sweeper.integration.test.ts +228 -0
  114. package/src/account-trial-warning-sweeper.ts +216 -0
  115. package/src/accounts-api.integration.test.ts +196 -0
  116. package/src/accounts-api.smoke.test.ts +292 -0
  117. package/src/accounts-api.ts +289 -0
  118. package/src/accounts-api.unit.test.ts +93 -0
  119. package/src/accounts.integration.test.ts +178 -0
  120. package/src/accounts.ts +208 -0
  121. package/src/admin-boot-config.ts +574 -0
  122. package/src/admin-boot-config.unit.test.ts +28 -0
  123. package/src/admin-boot.boot-order.unit.test.ts +91 -0
  124. package/src/admin-boot.ts +690 -0
  125. package/src/admin-dev-login.smoke.test.ts +317 -0
  126. package/src/admin-extension.ts +432 -0
  127. package/src/admin-extension.unit.test.ts +532 -0
  128. package/src/admin-local-agent.smoke.test.ts +1643 -0
  129. package/src/admin-spec.smoke.test.ts +96 -0
  130. package/src/admin-spec.ts +186 -0
  131. package/src/admin-ui-account-nav.ts +20 -0
  132. package/src/admin-ui-account-pages.ts +131 -0
  133. package/src/admin-ui-account-pages.unit.test.ts +112 -0
  134. package/src/admin-ui-account.smoke.test.ts +286 -0
  135. package/src/admin-ui-account.ts +185 -0
  136. package/src/admin-ui-accounts-pages.ts +202 -0
  137. package/src/admin-ui-accounts-pages.unit.test.ts +138 -0
  138. package/src/admin-ui-accounts.smoke.test.ts +434 -0
  139. package/src/admin-ui-accounts.ts +266 -0
  140. package/src/admin-ui-layout.ts +60 -0
  141. package/src/admin-ui-layout.unit.test.ts +140 -0
  142. package/src/admin-ui-pages.ts +3489 -0
  143. package/src/admin-ui-pages.unit.test.ts +4355 -0
  144. package/src/admin-ui-push-test.smoke.test.ts +125 -0
  145. package/src/admin-ui-push-test.ts +102 -0
  146. package/src/admin-ui-styles.ts +656 -0
  147. package/src/admin-ui-styles.unit.test.ts +103 -0
  148. package/src/admin-ui.fixtures.ts +457 -0
  149. package/src/admin-ui.smoke.test.ts +7227 -0
  150. package/src/admin-ui.ts +3824 -0
  151. package/src/agent-account-assignment.ts +49 -0
  152. package/src/agent-account-assignment.unit.test.ts +99 -0
  153. package/src/agent-anthropic-identity.integration.test.ts +99 -0
  154. package/src/agent-anthropic-identity.ts +78 -0
  155. package/src/agent-anthropic-identity.unit.test.ts +77 -0
  156. package/src/agent-ask-config.ts +59 -0
  157. package/src/agent-ask-config.unit.test.ts +70 -0
  158. package/src/agent-ask.integration.test.ts +175 -0
  159. package/src/agent-ask.smoke.test.ts +457 -0
  160. package/src/agent-ask.ts +378 -0
  161. package/src/agent-chat-tokens.integration.test.ts +420 -0
  162. package/src/agent-chat-tokens.smoke.test.ts +551 -0
  163. package/src/agent-chat-tokens.ts +259 -0
  164. package/src/agent-cron-jobs.integration.test.ts +582 -0
  165. package/src/agent-cron-jobs.ts +615 -0
  166. package/src/agent-cron-jobs.unit.test.ts +420 -0
  167. package/src/agent-cron-run-stats.integration.test.ts +1551 -0
  168. package/src/agent-cron-run-stats.smoke.test.ts +953 -0
  169. package/src/agent-cron-run-stats.ts +752 -0
  170. package/src/agent-cron-runs.integration.test.ts +1491 -0
  171. package/src/agent-cron-runs.ts +489 -0
  172. package/src/agent-deletion-checklist.ts +128 -0
  173. package/src/agent-deletion-checklist.unit.test.ts +201 -0
  174. package/src/agent-deletion-per-agent.integration.test.ts +323 -0
  175. package/src/agent-deletion.integration.test.ts +323 -0
  176. package/src/agent-deletion.ts +329 -0
  177. package/src/agent-deletion.unit.test.ts +412 -0
  178. package/src/agent-envs.integration.test.ts +323 -0
  179. package/src/agent-envs.ts +244 -0
  180. package/src/agent-github-installations.integration.test.ts +115 -0
  181. package/src/agent-github-installations.ts +52 -0
  182. package/src/agent-manifest.ts +685 -0
  183. package/src/agent-manifest.unit.test.ts +870 -0
  184. package/src/agent-members.ts +69 -0
  185. package/src/agent-members.unit.test.ts +250 -0
  186. package/src/agent-patch-author-allowlist.integration.test.ts +218 -0
  187. package/src/agent-plugins.integration.test.ts +157 -0
  188. package/src/agent-plugins.ts +95 -0
  189. package/src/agent-plugins.unit.test.ts +43 -0
  190. package/src/agent-provisioner-per-agent.integration.test.ts +949 -0
  191. package/src/agent-provisioner.integration.test.ts +1245 -0
  192. package/src/agent-provisioner.ts +1650 -0
  193. package/src/agent-provisioner.unit.test.ts +1852 -0
  194. package/src/agent-tokens.integration.test.ts +150 -0
  195. package/src/agent-tokens.ts +112 -0
  196. package/src/agent-tools.integration.test.ts +129 -0
  197. package/src/agent-tools.ts +111 -0
  198. package/src/agents-api.integration.test.ts +705 -0
  199. package/src/agents-api.sentry.smoke.test.ts +478 -0
  200. package/src/agents-api.smoke.test.ts +5986 -0
  201. package/src/agents-api.ts +2826 -0
  202. package/src/agents.integration.test.ts +512 -0
  203. package/src/agents.ts +1097 -0
  204. package/src/agents.unit.test.ts +1736 -0
  205. package/src/anthropic-credential-guard.ts +44 -0
  206. package/src/anthropic-credential-guard.unit.test.ts +88 -0
  207. package/src/anthropic-federated-token-provider.integration.test.ts +248 -0
  208. package/src/anthropic-federated-token-provider.ts +249 -0
  209. package/src/anthropic-identity-client.fixtures.ts +237 -0
  210. package/src/anthropic-identity-client.integration.test.ts +342 -0
  211. package/src/anthropic-identity-client.ts +473 -0
  212. package/src/anthropic-identity-fixture-spec.integration.test.ts +169 -0
  213. package/src/anthropic-identity-name.integration.test.ts +83 -0
  214. package/src/anthropic-identity-name.ts +163 -0
  215. package/src/anthropic-identity-name.unit.test.ts +345 -0
  216. package/src/anthropic-openapi-contract.fixtures.ts +192 -0
  217. package/src/api-auth.ts +383 -0
  218. package/src/api-auth.unit.test.ts +837 -0
  219. package/src/api.smoke.test.ts +1225 -0
  220. package/src/api.ts +376 -0
  221. package/src/attachment-validation.ts +124 -0
  222. package/src/attachment-validation.unit.test.ts +218 -0
  223. package/src/caller-scope.ts +131 -0
  224. package/src/caller-scope.unit.test.ts +116 -0
  225. package/src/chat-credential-config.ts +22 -0
  226. package/src/chat-credentials-logging.unit.test.ts +92 -0
  227. package/src/chat-markers.ts +114 -0
  228. package/src/chat-markers.unit.test.ts +116 -0
  229. package/src/chat-service-provisioning-client.integration.test.ts +412 -0
  230. package/src/chat-service-provisioning-client.ts +221 -0
  231. package/src/chat.smoke.test.ts +1875 -0
  232. package/src/clock.ts +47 -0
  233. package/src/coding-task-store-provisioning-client.integration.test.ts +372 -0
  234. package/src/coding-task-store-provisioning-client.ts +156 -0
  235. package/src/crypto.ts +83 -0
  236. package/src/dev-auth-guard.ts +29 -0
  237. package/src/dev-auth-guard.unit.test.ts +51 -0
  238. package/src/errors.ts +77 -0
  239. package/src/errors.unit.test.ts +52 -0
  240. package/src/fixtures/anthropic-identity-recorded-fixtures.json +850 -0
  241. package/src/fixtures/anthropic-openapi/SDK_VERSION +1 -0
  242. package/src/fixtures/anthropic-openapi/mock-spec.json.gz +0 -0
  243. package/src/fixtures/chat-service-provisioning-cassette.json +156 -0
  244. package/src/fixtures/github-app-manifest-cassette.json +44 -0
  245. package/src/fixtures/google-auth-cassette.json +36 -0
  246. package/src/fixtures/http-chat-client-cassette.json +226 -0
  247. package/src/fixtures/k8s-cassette.json +470 -0
  248. package/src/fixtures/okta-auth-cassette.json +36 -0
  249. package/src/fixtures/slack-provision-cassette.json +12 -0
  250. package/src/fixtures/slack-provisioning-http-cassette.json +98 -0
  251. package/src/fixtures/task-store-provisioning-cassette.json +85 -0
  252. package/src/github-app-provisioning-client.integration.test.ts +149 -0
  253. package/src/github-app-provisioning-client.ts +161 -0
  254. package/src/github-app-provisioning-client.unit.test.ts +114 -0
  255. package/src/github-provisioning-service.ts +569 -0
  256. package/src/github-provisioning-service.unit.test.ts +123 -0
  257. package/src/google-auth-client.integration.test.ts +195 -0
  258. package/src/google-auth-client.ts +121 -0
  259. package/src/http-chat-client.integration.test.ts +554 -0
  260. package/src/http-chat-client.ts +533 -0
  261. package/src/http-chat-client.unit.test.ts +119 -0
  262. package/src/import-boundary.unit.test.ts +240 -0
  263. package/src/kubernetes-client.integration.test.ts +697 -0
  264. package/src/kubernetes-client.ts +1027 -0
  265. package/src/kubernetes-client.unit.test.ts +726 -0
  266. package/src/main.smoke.test.ts +91 -0
  267. package/src/main.ts +21 -0
  268. package/src/main.unit.test.ts +801 -0
  269. package/src/okta-auth-client.integration.test.ts +263 -0
  270. package/src/okta-auth-client.ts +152 -0
  271. package/src/openapi-schemas.ts +1310 -0
  272. package/src/openapi-schemas.unit.test.ts +348 -0
  273. package/src/per-agent-identity.fixtures.ts +305 -0
  274. package/src/platform-doc-route.ts +40 -0
  275. package/src/prisma-client.integration.test.ts +57 -0
  276. package/src/prisma-client.ts +187 -0
  277. package/src/prisma-client.unit.test.ts +178 -0
  278. package/src/prisma-tx.ts +23 -0
  279. package/src/push-content.ts +177 -0
  280. package/src/push-content.unit.test.ts +213 -0
  281. package/src/push-crypto.ts +135 -0
  282. package/src/push-crypto.unit.test.ts +66 -0
  283. package/src/push-sender.ts +169 -0
  284. package/src/push-sender.unit.test.ts +107 -0
  285. package/src/push-service.ts +332 -0
  286. package/src/push-service.unit.test.ts +679 -0
  287. package/src/push-toggle.ts +135 -0
  288. package/src/push-toggle.unit.test.ts +78 -0
  289. package/src/pwa-https-gating.smoke.test.ts +229 -0
  290. package/src/pwa-icons.unit.test.ts +41 -0
  291. package/src/pwa-routes.smoke.test.ts +276 -0
  292. package/src/pwa.ts +380 -0
  293. package/src/pwa.unit.test.ts +321 -0
  294. package/src/schema.unit.test.ts +122 -0
  295. package/src/self-serve-config.ts +52 -0
  296. package/src/self-serve-config.unit.test.ts +79 -0
  297. package/src/server.smoke.test.ts +273 -0
  298. package/src/server.ts +50 -0
  299. package/src/service-integration.ts +67 -0
  300. package/src/service-integration.unit.test.ts +129 -0
  301. package/src/slack-provisioning-client.integration.test.ts +308 -0
  302. package/src/slack-provisioning-client.ts +411 -0
  303. package/src/slack-provisioning-client.unit.test.ts +80 -0
  304. package/src/slack-provisioning-service.integration.test.ts +637 -0
  305. package/src/slack-provisioning-service.ts +451 -0
  306. package/src/slack-provisioning.integration.test.ts +395 -0
  307. package/src/specialty-manifest-loader.fixture.integration.test.ts +63 -0
  308. package/src/specialty-manifest-loader.ts +255 -0
  309. package/src/specialty-manifest-loader.unit.test.ts +301 -0
  310. package/src/specialty-registry.schema.unit.test.ts +35 -0
  311. package/src/specialty-registry.ts +236 -0
  312. package/src/specialty-registry.unit.test.ts +272 -0
  313. package/src/startup-policy-reconcile.ts +77 -0
  314. package/src/startup-policy-reconcile.unit.test.ts +251 -0
  315. package/src/theme-tokens.json +12 -0
  316. package/src/token-crypto.ts +60 -0
  317. package/src/token-crypto.unit.test.ts +182 -0
  318. package/src/trial-expiry-sweeper.integration.test.ts +318 -0
  319. package/src/trial-expiry-sweeper.ts +132 -0
  320. package/src/trial-expiry-sweeper.unit.test.ts +391 -0
  321. package/src/trial-expiry-warning-sweeper.integration.test.ts +468 -0
  322. package/src/trial-expiry-warning-sweeper.ts +447 -0
  323. package/src/trial-expiry-warning-sweeper.unit.test.ts +148 -0
  324. package/src/voice-contract.unit.test.ts +102 -0
  325. package/tsconfig.json +9 -0
@@ -0,0 +1,656 @@
1
+ import {
2
+ escapeHtml,
3
+ renderShipwrightToolbar,
4
+ baseStyles as toolbarBaseStyles,
5
+ } from "@app-vitals/agent-lib/web/toolbar.ts";
6
+ import { shouldShowAccountNav } from "./admin-ui-account-nav.ts";
7
+
8
+ export { escapeHtml };
9
+
10
+ /**
11
+ * Shared responsive breakpoint constants (px), interpolated into `@media`
12
+ * template strings across admin-ui-styles.ts and admin-ui-pages.ts so tests
13
+ * can assert against the actual numeric constant instead of grepping for a
14
+ * hardcoded literal like "640px" in the rendered HTML string.
15
+ */
16
+ export const BREAKPOINT_MOBILE_MAX = 640;
17
+ export const BREAKPOINT_TABLET_MAX = 960;
18
+
19
+ export function baseStyles(): string {
20
+ return `${toolbarBaseStyles()}
21
+ /* ─── Page header ───────────────────────────────────── */
22
+ .page-header {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ margin-bottom: 24px;
27
+ }
28
+ .page-title {
29
+ font-size: 22px;
30
+ font-weight: 700;
31
+ color: #1a1a2e;
32
+ }
33
+
34
+ /* ─── Buttons ───────────────────────────────────────── */
35
+ .btn {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 6px;
39
+ font-size: 13px;
40
+ font-weight: 500;
41
+ padding: 7px 14px;
42
+ border-radius: 6px;
43
+ border: 1px solid transparent;
44
+ cursor: pointer;
45
+ font-family: inherit;
46
+ text-decoration: none;
47
+ }
48
+ .btn-primary {
49
+ background: #1a1a2e;
50
+ color: #fff;
51
+ border-color: #1a1a2e;
52
+ }
53
+ .btn-primary:hover { background: #2d2d4e; }
54
+ .btn-secondary {
55
+ background: #fff;
56
+ color: #1a1a2e;
57
+ border-color: #e8e8ee;
58
+ }
59
+ .btn-secondary:hover { background: #f3f4f6; }
60
+ .btn-danger {
61
+ background: #fff;
62
+ color: #dc2626;
63
+ border-color: #fee2e2;
64
+ }
65
+ .btn-danger:hover { background: #fee2e2; }
66
+
67
+ /* ─── Cards / panels ────────────────────────────────── */
68
+ .card {
69
+ background: #fff;
70
+ border: 1px solid #e8e8ee;
71
+ border-radius: 10px;
72
+ padding: 20px 24px;
73
+ margin-bottom: 16px;
74
+ }
75
+ .card-title {
76
+ font-size: 14px;
77
+ font-weight: 600;
78
+ color: #1a1a2e;
79
+ margin-bottom: 16px;
80
+ padding-bottom: 12px;
81
+ border-bottom: 1px solid #f3f4f6;
82
+ }
83
+
84
+ /* ─── Agent detail page — accordion groups (AGA-1.1) ─── */
85
+ /* Each group is a details element (class="group") wrapping several
86
+ existing .card sections. The toggle glyph is a ::before on .group-title (the
87
+ flush-left flex child) rather than on .group-summary itself -- a
88
+ ::before on the flex summary would become its own flex item and
89
+ break the flush-left/flush-right 2-sided layout into an uneven
90
+ 3-way split. See .more-filters below for the anti-pattern this
91
+ avoids (::before directly on a non-flex <summary> is fine there;
92
+ .group-summary is display:flex, which is the difference). */
93
+ .group {
94
+ margin-bottom: 16px;
95
+ }
96
+ .group-summary {
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: space-between;
100
+ gap: 12px;
101
+ background: #fff;
102
+ border: 1px solid #e8e8ee;
103
+ border-radius: 10px;
104
+ padding: 14px 24px;
105
+ cursor: pointer;
106
+ list-style: none;
107
+ }
108
+ .group-summary::-webkit-details-marker { display: none; }
109
+ .group[open] > .group-summary {
110
+ border-bottom-left-radius: 0;
111
+ border-bottom-right-radius: 0;
112
+ }
113
+ .group-title {
114
+ font-size: 14px;
115
+ font-weight: 600;
116
+ color: #1a1a2e;
117
+ }
118
+ .group-title::before {
119
+ content: "▸";
120
+ display: inline-block;
121
+ margin-right: 8px;
122
+ transition: transform 0.15s ease;
123
+ }
124
+ .group[open] > .group-summary .group-title::before {
125
+ transform: rotate(90deg);
126
+ }
127
+ .group-sub {
128
+ font-size: 12px;
129
+ color: #6b7280;
130
+ white-space: nowrap;
131
+ }
132
+ .group-body {
133
+ border: 1px solid #e8e8ee;
134
+ border-top: none;
135
+ border-radius: 0 0 10px 10px;
136
+ padding: 16px 24px 4px;
137
+ background: #fafafa;
138
+ }
139
+ .group-body .card:last-child {
140
+ margin-bottom: 16px;
141
+ }
142
+
143
+ /* ─── Agent detail page — stat strip (AGA-1.2) ──────────── */
144
+ .stat-strip {
145
+ display: grid;
146
+ grid-template-columns: repeat(4, 1fr);
147
+ gap: 16px;
148
+ margin-bottom: 24px;
149
+ }
150
+ .stat-tile {
151
+ background: #fff;
152
+ border: 1px solid #e8e8ee;
153
+ border-radius: 10px;
154
+ padding: 16px;
155
+ text-align: center;
156
+ }
157
+ .stat-tile-label {
158
+ font-size: 12px;
159
+ font-weight: 600;
160
+ color: #6b7280;
161
+ text-transform: uppercase;
162
+ letter-spacing: 0.5px;
163
+ margin-bottom: 8px;
164
+ }
165
+ .stat-tile-value {
166
+ font-size: 28px;
167
+ font-weight: 700;
168
+ color: #1a1a2e;
169
+ }
170
+ .stat-tile-value span {
171
+ display: block;
172
+ }
173
+ .stat-tile-warning {
174
+ background: #fef3c7;
175
+ border-color: #fde68a;
176
+ }
177
+ .stat-tile-warning .stat-tile-label {
178
+ color: #92400e;
179
+ }
180
+ .stat-tile-warning .stat-tile-value {
181
+ color: #92400e;
182
+ }
183
+
184
+ /* ─── Tables ────────────────────────────────────────── */
185
+ .data-table {
186
+ width: 100%;
187
+ border-collapse: collapse;
188
+ font-size: 13px;
189
+ }
190
+ .data-table th {
191
+ text-align: left;
192
+ font-size: 11px;
193
+ font-weight: 600;
194
+ text-transform: uppercase;
195
+ letter-spacing: 0.5px;
196
+ color: #9ca3af;
197
+ padding: 8px 12px;
198
+ border-bottom: 1px solid #f3f4f6;
199
+ }
200
+ .data-table td {
201
+ padding: 10px 12px;
202
+ border-bottom: 1px solid #f3f4f6;
203
+ color: #374151;
204
+ vertical-align: middle;
205
+ }
206
+ .data-table tr:last-child td {
207
+ border-bottom: none;
208
+ }
209
+ .data-table tr:hover td {
210
+ background: #fafafa;
211
+ }
212
+
213
+ /* ─── Agent list ────────────────────────────────────── */
214
+ .agent-link {
215
+ font-weight: 500;
216
+ color: #1a1a2e;
217
+ text-decoration: none;
218
+ }
219
+ .agent-link:hover { text-decoration: underline; }
220
+
221
+ /* ─── Forms ─────────────────────────────────────────── */
222
+ .form-group {
223
+ margin-bottom: 16px;
224
+ }
225
+ .form-label {
226
+ display: block;
227
+ font-size: 13px;
228
+ font-weight: 500;
229
+ color: #374151;
230
+ margin-bottom: 6px;
231
+ }
232
+ .form-input {
233
+ width: 100%;
234
+ padding: 8px 12px;
235
+ font-size: 14px;
236
+ font-family: inherit;
237
+ border: 1px solid #e8e8ee;
238
+ border-radius: 6px;
239
+ background: #fff;
240
+ color: #1a1a2e;
241
+ }
242
+ .form-input:focus {
243
+ outline: none;
244
+ border-color: #1a1a2e;
245
+ }
246
+ .form-row {
247
+ display: flex;
248
+ gap: 8px;
249
+ align-items: flex-end;
250
+ }
251
+ .form-row .form-group {
252
+ flex: 1;
253
+ margin-bottom: 0;
254
+ }
255
+
256
+ /* ─── Login page ────────────────────────────────────── */
257
+ .login-wrapper {
258
+ min-height: 100vh;
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ }
263
+ .login-card {
264
+ background: #fff;
265
+ border: 1px solid #e8e8ee;
266
+ border-radius: 12px;
267
+ padding: 40px;
268
+ width: 100%;
269
+ max-width: 400px;
270
+ }
271
+ .login-title {
272
+ font-size: 20px;
273
+ font-weight: 700;
274
+ margin-bottom: 8px;
275
+ }
276
+ .login-subtitle {
277
+ font-size: 13px;
278
+ color: #6b7280;
279
+ margin-bottom: 28px;
280
+ }
281
+
282
+ /* ─── Alerts ────────────────────────────────────────── */
283
+ .alert {
284
+ padding: 10px 14px;
285
+ border-radius: 6px;
286
+ font-size: 13px;
287
+ margin-bottom: 16px;
288
+ }
289
+ .alert-error {
290
+ background: #fee2e2;
291
+ color: #dc2626;
292
+ border: 1px solid #fecaca;
293
+ }
294
+ .alert-success {
295
+ background: #d1fae5;
296
+ color: #065f46;
297
+ border: 1px solid #a7f3d0;
298
+ }
299
+ .alert-warning {
300
+ background: #fef3c7;
301
+ color: #92400e;
302
+ border: 1px solid #fde68a;
303
+ }
304
+
305
+ /* ─── Badges ────────────────────────────────────────── */
306
+ .badge {
307
+ display: inline-flex;
308
+ align-items: center;
309
+ font-size: 11px;
310
+ font-weight: 600;
311
+ padding: 2px 8px;
312
+ border-radius: 9999px;
313
+ }
314
+ .badge-green {
315
+ background: #d1fae5;
316
+ color: #065f46;
317
+ }
318
+ .badge-gray {
319
+ background: #f3f4f6;
320
+ color: #6b7280;
321
+ }
322
+ .badge-warning {
323
+ background: #fde68a;
324
+ color: #92400e;
325
+ }
326
+
327
+ /* ─── Scope pills (filter chips) ────────────────────── */
328
+ .scope-pill {
329
+ display: inline-flex;
330
+ align-items: center;
331
+ gap: 4px;
332
+ font-size: 11px;
333
+ font-weight: 500;
334
+ padding: 3px 10px;
335
+ border-radius: 9999px;
336
+ background: #f3f4f6;
337
+ color: #374151;
338
+ border: 1px solid #e5e7eb;
339
+ }
340
+ .scope-pill.active {
341
+ background: #eef2ff;
342
+ color: #4338ca;
343
+ border-color: #c7d2fe;
344
+ }
345
+ .scope-pill-remove {
346
+ cursor: pointer;
347
+ color: #9ca3af;
348
+ font-weight: 700;
349
+ line-height: 1;
350
+ }
351
+ .scope-pill-remove:hover { color: #374151; }
352
+
353
+ /* Org/Repo multiselect filters (renderRepoOrgFilterFields) restyled as
354
+ pill/tag-style fields (AXR-1.1). These stay two independent
355
+ <select multiple> controls -- not merged into one combined pill. */
356
+ .scope-select-group {
357
+ display: flex;
358
+ flex-direction: column;
359
+ gap: 4px;
360
+ }
361
+ .scope-select {
362
+ border-radius: 10px;
363
+ background: #f9fafb;
364
+ }
365
+ .scope-select option:checked {
366
+ background: #eef2ff;
367
+ color: #4338ca;
368
+ }
369
+
370
+ /* ─── Board / column / card layout ──────────────────── */
371
+ .board {
372
+ display: flex;
373
+ gap: 16px;
374
+ align-items: flex-start;
375
+ overflow-x: auto;
376
+ padding-bottom: 8px;
377
+ }
378
+ .column {
379
+ flex: 0 0 280px;
380
+ display: flex;
381
+ flex-direction: column;
382
+ gap: 10px;
383
+ background: #f9fafb;
384
+ border: 1px solid #e8e8ee;
385
+ border-radius: 10px;
386
+ padding: 12px;
387
+ }
388
+ .column-header {
389
+ display: flex;
390
+ align-items: center;
391
+ justify-content: space-between;
392
+ font-size: 12px;
393
+ font-weight: 600;
394
+ color: #6b7280;
395
+ text-transform: uppercase;
396
+ letter-spacing: 0.5px;
397
+ margin-bottom: 4px;
398
+ }
399
+ .column-count {
400
+ font-size: 11px;
401
+ font-weight: 600;
402
+ color: #9ca3af;
403
+ background: #fff;
404
+ border: 1px solid #e8e8ee;
405
+ border-radius: 9999px;
406
+ padding: 1px 8px;
407
+ }
408
+ /* Individual board items reuse .card for the panel look; this modifier
409
+ tightens padding/margins for the denser board context. */
410
+ .column .card {
411
+ margin-bottom: 0;
412
+ padding: 12px 14px;
413
+ }
414
+
415
+ /* ─── Additional badge variants (beyond badge-hitl/badge-dep) ───────── */
416
+ .badge-purple {
417
+ background: #ede9fe;
418
+ color: #5b21b6;
419
+ }
420
+ .badge-teal {
421
+ background: #ccfbf1;
422
+ color: #0f766e;
423
+ }
424
+
425
+ /* ─── Header tooltip (hover, data-tip attribute) ────── */
426
+ .header-tooltip {
427
+ position: relative;
428
+ cursor: help;
429
+ border-bottom: 1px dotted #9ca3af;
430
+ }
431
+ .header-tooltip[data-tip]:hover::after {
432
+ content: attr(data-tip);
433
+ position: absolute;
434
+ bottom: 125%;
435
+ left: 50%;
436
+ transform: translateX(-50%);
437
+ white-space: nowrap;
438
+ background: #1a1a2e;
439
+ color: #fff;
440
+ font-size: 11px;
441
+ font-weight: 500;
442
+ padding: 4px 8px;
443
+ border-radius: 6px;
444
+ z-index: 10;
445
+ pointer-events: none;
446
+ }
447
+
448
+ /* ─── Heartbeat status dots (fresh/aging/stale) ─────── */
449
+ .heartbeat-dot {
450
+ display: inline-block;
451
+ width: 8px;
452
+ height: 8px;
453
+ border-radius: 9999px;
454
+ background: #d1d5db;
455
+ }
456
+ .heartbeat-dot.fresh { background: #22c55e; }
457
+ .heartbeat-dot.aging { background: #f59e0b; }
458
+ .heartbeat-dot.stale { background: #ef4444; }
459
+
460
+ /* ─── More filters disclosure ───────────────────────── */
461
+ .more-filters {
462
+ margin-top: 8px;
463
+ }
464
+ .more-filters summary {
465
+ cursor: pointer;
466
+ font-size: 12px;
467
+ font-weight: 500;
468
+ color: #6b7280;
469
+ list-style: none;
470
+ }
471
+ .more-filters summary::-webkit-details-marker { display: none; }
472
+ .more-filters summary::before {
473
+ content: "▸";
474
+ display: inline-block;
475
+ margin-right: 4px;
476
+ transition: transform 0.15s ease;
477
+ }
478
+ .more-filters[open] summary::before {
479
+ transform: rotate(90deg);
480
+ }
481
+ .more-filters[open] summary {
482
+ color: #1a1a2e;
483
+ }
484
+ /* Active-filter count badge — visible whether collapsed or open. */
485
+ .more-filters summary .badge {
486
+ margin-left: 6px;
487
+ vertical-align: middle;
488
+ }
489
+ .more-filters-panel {
490
+ display: flex;
491
+ gap: 8px;
492
+ flex-wrap: wrap;
493
+ margin-top: 8px;
494
+ }
495
+
496
+ /* ─── Provision page ────────────────────────────────── */
497
+ .provision-steps {
498
+ display: flex;
499
+ gap: 8px;
500
+ margin-bottom: 24px;
501
+ }
502
+ .provision-step {
503
+ font-size: 12px;
504
+ font-weight: 500;
505
+ color: #9ca3af;
506
+ padding: 4px 10px;
507
+ border-radius: 4px;
508
+ background: #f3f4f6;
509
+ }
510
+ .provision-step.active {
511
+ background: #1a1a2e;
512
+ color: #fff;
513
+ }
514
+ .oauth-url-box {
515
+ word-break: break-all;
516
+ background: #f9fafb;
517
+ border: 1px solid #e8e8ee;
518
+ border-radius: 6px;
519
+ padding: 12px 14px;
520
+ font-size: 13px;
521
+ font-family: monospace;
522
+ margin-bottom: 16px;
523
+ }
524
+
525
+ /* ─── Empty states ──────────────────────────────────── */
526
+ .empty-state {
527
+ text-align: center;
528
+ padding: 32px;
529
+ color: #9ca3af;
530
+ font-size: 13px;
531
+ }
532
+
533
+ /* ─── Code / monospace ──────────────────────────────── */
534
+ .mono {
535
+ font-family: "SF Mono", "Fira Code", Menlo, monospace;
536
+ font-size: 12px;
537
+ }
538
+
539
+ /* ─── Responsive ────────────────────────────────────── */
540
+ html, body {
541
+ overflow-x: hidden;
542
+ max-width: 100%;
543
+ }
544
+ .data-table-wrapper {
545
+ overflow-x: auto;
546
+ -webkit-overflow-scrolling: touch;
547
+ /* Scroll-shadow: fades appear at edges when content overflows */
548
+ background-color: #fff;
549
+ background-image:
550
+ linear-gradient(to right, #fff, #fff),
551
+ linear-gradient(to right, rgba(0,0,0,0.05), transparent),
552
+ linear-gradient(to left, #fff, #fff),
553
+ linear-gradient(to left, rgba(0,0,0,0.05), transparent);
554
+ background-position: left, left, right, right;
555
+ background-size: 20px 100%, 20px 100%, 24px 100%, 24px 100%;
556
+ background-repeat: no-repeat;
557
+ background-attachment: local, scroll, local, scroll;
558
+ }
559
+ .state-tab { padding: 5px 14px; }
560
+
561
+ @media (max-width: ${BREAKPOINT_MOBILE_MAX}px) {
562
+ .col-session, .col-repo, .col-source, .col-created { display: none; }
563
+ .col-review-cycles, .col-patch-cycles, .col-claimed-by { display: none; }
564
+ .col-tokens, .col-model { display: none; }
565
+ .state-tab { padding: 13px 14px; }
566
+ .vos-page {
567
+ padding: 16px 12px 48px;
568
+ }
569
+ .card {
570
+ padding: 14px 12px;
571
+ }
572
+ .group-summary {
573
+ padding: 12px 14px;
574
+ }
575
+ .group-body {
576
+ padding: 12px 12px 4px;
577
+ }
578
+ .form-row {
579
+ flex-wrap: wrap;
580
+ }
581
+ .vos-toolbar {
582
+ padding: 0 12px;
583
+ gap: 12px;
584
+ }
585
+ .data-table-wrapper .data-table th,
586
+ .data-table-wrapper .data-table td {
587
+ white-space: nowrap;
588
+ }
589
+ /* Let the table bleed to card edges on mobile so scroll shadow aligns */
590
+ .card .data-table-wrapper {
591
+ margin: 0 -12px;
592
+ padding: 0 12px;
593
+ }
594
+ .detail-table td:first-child {
595
+ display: block;
596
+ width: auto;
597
+ white-space: normal;
598
+ }
599
+ .detail-table td:last-child {
600
+ display: block;
601
+ width: auto;
602
+ padding-top: 0;
603
+ }
604
+
605
+ /* iOS Safari zooms in on focus for any input under 16px font-size. */
606
+ .form-input,
607
+ input[type="text"],
608
+ input[type="search"],
609
+ input[type="email"],
610
+ input[type="password"],
611
+ input[type="number"],
612
+ textarea,
613
+ select {
614
+ font-size: 16px;
615
+ }
616
+
617
+ /* Touch targets: 44px minimum. .data-table .btn is excluded — a full
618
+ 44px target there would make the tasks/PRs tables absurdly tall —
619
+ via an explicit override pinning it back to its current ~36px.
620
+ Both selectors are specificity 0,2,0 vs 0,1,0, so .data-table .btn
621
+ always wins regardless of declaration order within this block. */
622
+ .btn {
623
+ min-height: 44px;
624
+ align-items: center;
625
+ }
626
+ .data-table .btn {
627
+ min-height: 36px;
628
+ }
629
+ .thread-pane-link {
630
+ min-height: 44px;
631
+ display: flex;
632
+ align-items: center;
633
+ }
634
+ }
635
+
636
+ /* ─── Tablet band (641–${BREAKPOINT_TABLET_MAX}px) ──────────────── */
637
+ @media (min-width: ${BREAKPOINT_MOBILE_MAX + 1}px) and (max-width: ${BREAKPOINT_TABLET_MAX}px) {
638
+ .vos-page {
639
+ padding: 24px 20px 56px;
640
+ }
641
+ .card {
642
+ padding: 16px 18px;
643
+ }
644
+ }
645
+ `;
646
+ }
647
+
648
+ export function renderAdminToolbar(userName: string, activePath = ""): string {
649
+ return renderShipwrightToolbar({
650
+ userName,
651
+ activePath,
652
+ logoutAction: "/admin/logout",
653
+ metricsUrl: process.env.METRICS_DASHBOARD_URL ?? "/dashboard",
654
+ showAccount: shouldShowAccountNav(),
655
+ });
656
+ }