fleetlens 0.13.2 → 0.15.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.
- package/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/app-path-routes-manifest.json +4 -1
- package/app/apps/web/.next/build-manifest.json +3 -3
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/routes-manifest.json +21 -3
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js +3 -3
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js +4 -3
- package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team/autostart/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js +7 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js +8 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js +8 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/changelog/page.js +1 -1
- package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page.js +2 -2
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -2
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +3 -3
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/settings/page.js +2 -2
- package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/onboarding/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js +19 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js.map +5 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/team/page.js +3 -2
- package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app-paths-manifest.json +4 -1
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_team_onboarding_start_route_actions_0zeptl_.js +3 -0
- package/app/apps/web/.next/server/chunks/0fuv_zod_v4_classic_external_0.z.vap.js +39 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__038-.1r._.js +6 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0~utv~v._.js → [root-of-the-server]__03ewxxu._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__03rmbz6._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__065lib8._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0e.f-t.._.js → [root-of-the-server]__0a0~7d6._.js} +2 -2
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0gril0u._.js → [root-of-the-server]__0acikq.._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0et2sqb._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[externals]__09v47rg._.js → [root-of-the-server]__0jbc7tz._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mq0or2._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mvl0qn._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0n79hpm._.js → [root-of-the-server]__0ow1kn1._.js} +4 -4
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~xt99s._.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_autostart_route_actions_10-4s71.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_sync-projects_route_actions_0r_2f5n.js +3 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_036my~r._.js +13 -0
- package/app/apps/web/.next/server/chunks/packages_09wmnv.._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_0o6.gpk._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0z7mg0b._.js → [root-of-the-server]__08l86e-._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0a7n44-._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0c00i76._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0i.q-1h._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0r4tks8._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0stu~~r._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ufi3.w._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0o03ymy._.js → [root-of-the-server]__10~b4bf._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_0.97l5.._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{apps_web_app_settings_0u6z6rj._.js → apps_web_0q2x4a~._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/apps_web_114yo0v._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_team_onboarding_page_actions_0pss~.k.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0c-tpft._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_team-config_ts_10egg08._.js +39 -0
- package/app/apps/web/.next/server/chunks/ssr/{node_modules_next_dist_0t-hj0x._.js → node_modules_0yrkyef._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0jxvcfi.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0s5yhs4.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
- package/app/apps/web/.next/server/functions-config-manifest.json +3 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/app/apps/web/.next/server/middleware-manifest.json +5 -5
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/{0wwcx42ng8i2e.js → 0-3_u.90d79wi.js} +1 -1
- package/app/apps/web/.next/static/chunks/00a9oyw~h5i1p.js +1 -0
- package/app/apps/web/.next/static/chunks/040s42ypidk7s.css +1 -0
- package/app/apps/web/.next/static/chunks/06eiwghjkkbnh.js +1 -0
- package/app/apps/web/.next/static/chunks/06qeg4pt5jdqs.js +1 -0
- package/app/apps/web/.next/static/chunks/{0e_fzy0n8ij-o.js → 0u6gxpqhl0gc7.js} +1 -1
- package/app/apps/web/.next/static/chunks/18cj08-la37o-.js +1 -0
- package/app/apps/web/app/api/team/autostart/route.ts +41 -0
- package/app/apps/web/app/api/team/onboarding/start/route.ts +96 -0
- package/app/apps/web/app/api/team/sync-projects/route.ts +39 -0
- package/app/apps/web/app/globals.css +82 -0
- package/app/apps/web/app/settings/ai-features-form.tsx +1 -1
- package/app/apps/web/app/settings/page.tsx +21 -7
- package/app/apps/web/app/{settings → team}/force-sync-button.tsx +2 -4
- package/app/apps/web/app/team/onboarding/onboarding-wizard.tsx +503 -0
- package/app/apps/web/app/team/onboarding/page.tsx +28 -0
- package/app/apps/web/app/team/page.tsx +60 -81
- package/app/apps/web/app/{settings/team-connection-section.tsx → team/sync-activity-section.tsx} +6 -23
- package/app/apps/web/app/team/synced-projects-form.tsx +201 -0
- package/app/apps/web/app/team/synced-projects-section.tsx +37 -0
- package/app/apps/web/components/autostart-toggle.tsx +59 -0
- package/app/apps/web/components/previous-cycles-trend.tsx +4 -1
- package/app/apps/web/components/project-sync-picker.tsx +109 -0
- package/app/apps/web/components/team-chip.tsx +1 -1
- package/app/apps/web/components/team-welcome-banner.tsx +1 -1
- package/app/apps/web/lib/autostart-data.ts +31 -0
- package/app/apps/web/lib/changelog-data.generated.ts +1 -1
- package/app/apps/web/lib/sync-projects-data.ts +36 -0
- package/app/apps/web/lib/team-config.ts +21 -4
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +2089 -1622
- package/dist/index.js +1462 -230
- package/package.json +1 -1
- package/app/apps/web/.next/server/app/api/team-config/route/app-paths-manifest.json +0 -3
- package/app/apps/web/.next/server/app/api/team-config/route.js +0 -8
- package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +0 -1
- package/app/apps/web/.next/server/app/api/team-config/route_client-reference-manifest.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__08agijy._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0atn.dt._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0b.b7~1._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0i06ito._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0y5p73y._.js +0 -3
- package/app/apps/web/.next/server/chunks/_0euqdko._.js +0 -49
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team-config_route_actions_0ad2gak.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_0c-sz9n._.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_0wywuu.._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__09yxy53._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0q9qy0z._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_team_team-sync-form_tsx_0choz0h._.js +0 -3
- package/app/apps/web/.next/static/chunks/083~hzkv577.5.css +0 -1
- package/app/apps/web/.next/static/chunks/0h.ua.-myj.6z.js +0 -1
- package/app/apps/web/.next/static/chunks/0uyctl-m8iydb.js +0 -1
- package/app/apps/web/.next/static/chunks/14joqbsr9ihhu.js +0 -1
- package/app/apps/web/app/api/team-config/route.ts +0 -39
- package/app/apps/web/app/team/team-sync-form.tsx +0 -144
- /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route/build-manifest.json +0 -0
- /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route/server-reference-manifest.json +0 -0
- /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route.js.map +0 -0
- /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_ssgManifest.js +0 -0
|
@@ -298,6 +298,88 @@ input::placeholder {
|
|
|
298
298
|
border-color: var(--af-surface-elevated);
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/* Tailwind v4 preflight defaults `border` utilities to currentColor, which
|
|
302
|
+
reads as a harsh black/white line. Fall back to the theme token. */
|
|
303
|
+
@layer base {
|
|
304
|
+
*,
|
|
305
|
+
::after,
|
|
306
|
+
::before {
|
|
307
|
+
border-color: var(--af-border-subtle);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Buttons + panels for management surfaces (team, settings, wizard). */
|
|
312
|
+
.af-btn,
|
|
313
|
+
.af-btn-primary,
|
|
314
|
+
.af-btn-danger {
|
|
315
|
+
display: inline-block;
|
|
316
|
+
border-radius: 8px;
|
|
317
|
+
padding: 6px 14px;
|
|
318
|
+
font-size: 13px;
|
|
319
|
+
transition: background 0.12s, border-color 0.12s;
|
|
320
|
+
}
|
|
321
|
+
.af-btn {
|
|
322
|
+
border: 1px solid var(--af-border-subtle);
|
|
323
|
+
background: var(--af-surface);
|
|
324
|
+
color: var(--af-text);
|
|
325
|
+
}
|
|
326
|
+
.af-btn:hover:not(:disabled) {
|
|
327
|
+
border-color: var(--af-accent);
|
|
328
|
+
}
|
|
329
|
+
.af-btn-primary {
|
|
330
|
+
border: 1px solid transparent;
|
|
331
|
+
background: var(--af-accent);
|
|
332
|
+
color: var(--background);
|
|
333
|
+
font-weight: 500;
|
|
334
|
+
}
|
|
335
|
+
.af-btn-primary:hover:not(:disabled) {
|
|
336
|
+
background: var(--af-accent-hover);
|
|
337
|
+
}
|
|
338
|
+
.af-btn-danger {
|
|
339
|
+
border: 1px solid var(--af-danger);
|
|
340
|
+
background: transparent;
|
|
341
|
+
color: var(--af-danger);
|
|
342
|
+
}
|
|
343
|
+
.af-btn-danger:hover:not(:disabled) {
|
|
344
|
+
background: var(--af-danger-subtle);
|
|
345
|
+
}
|
|
346
|
+
.af-btn:disabled,
|
|
347
|
+
.af-btn-primary:disabled,
|
|
348
|
+
.af-btn-danger:disabled {
|
|
349
|
+
opacity: 0.55;
|
|
350
|
+
cursor: default;
|
|
351
|
+
}
|
|
352
|
+
.af-panel {
|
|
353
|
+
background: var(--af-surface-elevated);
|
|
354
|
+
border: 1px solid var(--af-border-subtle);
|
|
355
|
+
border-radius: 8px;
|
|
356
|
+
}
|
|
357
|
+
.af-panel-danger {
|
|
358
|
+
background: var(--af-danger-subtle);
|
|
359
|
+
border: 1px solid var(--af-danger);
|
|
360
|
+
border-radius: 8px;
|
|
361
|
+
color: var(--af-danger);
|
|
362
|
+
}
|
|
363
|
+
.af-step-active {
|
|
364
|
+
background: var(--af-accent);
|
|
365
|
+
color: var(--background);
|
|
366
|
+
}
|
|
367
|
+
.af-chip {
|
|
368
|
+
display: inline-block;
|
|
369
|
+
border: 1px solid var(--af-border-subtle);
|
|
370
|
+
border-radius: 9999px;
|
|
371
|
+
padding: 2px 10px;
|
|
372
|
+
font-family: var(--font-mono);
|
|
373
|
+
font-size: 12px;
|
|
374
|
+
}
|
|
375
|
+
a.af-chip:hover {
|
|
376
|
+
border-color: var(--af-accent);
|
|
377
|
+
text-decoration: underline;
|
|
378
|
+
}
|
|
379
|
+
.af-row:hover {
|
|
380
|
+
background: var(--af-surface-hover);
|
|
381
|
+
}
|
|
382
|
+
|
|
301
383
|
/* =============================================
|
|
302
384
|
Markdown prose (sessions drawer)
|
|
303
385
|
============================================= */
|
|
@@ -72,7 +72,7 @@ export function AiFeaturesForm({ initial }: { initial: Initial }) {
|
|
|
72
72
|
<button
|
|
73
73
|
type="submit"
|
|
74
74
|
disabled={saving}
|
|
75
|
-
className="
|
|
75
|
+
className="af-btn-primary"
|
|
76
76
|
>
|
|
77
77
|
{saving ? "Saving…" : "Save"}
|
|
78
78
|
</button>
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import { readSettings } from "@claude-lens/entries/node";
|
|
2
2
|
import { AiFeaturesForm } from "./ai-features-form";
|
|
3
|
-
import {
|
|
3
|
+
import { readAutostartState } from "@/lib/autostart-data";
|
|
4
|
+
import { AutostartToggle } from "@/components/autostart-toggle";
|
|
4
5
|
|
|
6
|
+
// Layout mirrors /team: max-w-3xl, header + subtitle, card-style sections.
|
|
5
7
|
export default function SettingsPage() {
|
|
6
8
|
const s = readSettings();
|
|
7
9
|
return (
|
|
8
|
-
<main className="mx-auto max-w-
|
|
9
|
-
<
|
|
10
|
+
<main className="mx-auto max-w-3xl p-6 space-y-8">
|
|
11
|
+
<header>
|
|
12
|
+
<h1 className="text-2xl font-semibold">Settings</h1>
|
|
13
|
+
<p className="text-sm text-gray-500 mt-1">
|
|
14
|
+
Local machine preferences. Team-sync management lives on the Team page.
|
|
15
|
+
</p>
|
|
16
|
+
</header>
|
|
10
17
|
|
|
11
|
-
<
|
|
18
|
+
<section className="af-card space-y-3">
|
|
19
|
+
<h2 className="text-lg font-medium">Daemon auto-start</h2>
|
|
20
|
+
<p className="text-sm text-gray-500">
|
|
21
|
+
Brings the dashboard, plan-usage polling, and team sync back after a
|
|
22
|
+
reboot. Team pairing turns this on by default.
|
|
23
|
+
</p>
|
|
24
|
+
<AutostartToggle initial={readAutostartState()} />
|
|
25
|
+
</section>
|
|
12
26
|
|
|
13
|
-
<section>
|
|
14
|
-
<h2 className="text-lg font-medium
|
|
15
|
-
<p className="text-sm text-gray-500
|
|
27
|
+
<section className="af-card space-y-3">
|
|
28
|
+
<h2 className="text-lg font-medium">AI Features</h2>
|
|
29
|
+
<p className="text-sm text-gray-500">
|
|
16
30
|
When enabled, Fleetlens synthesizes daily digests and per-entry
|
|
17
31
|
narratives by spawning your local <code>claude</code> CLI (uses your
|
|
18
32
|
existing Claude Code auth — no API key required).
|
|
@@ -49,7 +49,7 @@ export function ForceSyncButton({ cliAvailable }: { cliAvailable: boolean }) {
|
|
|
49
49
|
? "Force sync is only available when running via the fleetlens CLI"
|
|
50
50
|
: undefined
|
|
51
51
|
}
|
|
52
|
-
className="
|
|
52
|
+
className="af-btn disabled:cursor-not-allowed"
|
|
53
53
|
>
|
|
54
54
|
{state === "syncing" ? "Syncing…" : "Sync now"}
|
|
55
55
|
</button>
|
|
@@ -60,9 +60,7 @@ export function ForceSyncButton({ cliAvailable }: { cliAvailable: boolean }) {
|
|
|
60
60
|
|
|
61
61
|
{result && (
|
|
62
62
|
<pre
|
|
63
|
-
className={`
|
|
64
|
-
result.ok ? "border-gray-200 bg-gray-50" : "border-red-300 bg-red-50 text-red-700"
|
|
65
|
-
}`}
|
|
63
|
+
className={`p-3 text-xs font-mono whitespace-pre-wrap ${result.ok ? "af-panel" : "af-panel-danger"}`}
|
|
66
64
|
>
|
|
67
65
|
{result.lines.length > 0 ? result.lines.join("\n") : ""}
|
|
68
66
|
{result.error ? (result.lines.length > 0 ? "\n" : "") + result.error : ""}
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { ProjectSyncPicker } from "@/components/project-sync-picker";
|
|
5
|
+
import type { SyncProjectRow } from "@/lib/sync-projects-data";
|
|
6
|
+
import type { SyncProjects } from "@/lib/team-config";
|
|
7
|
+
|
|
8
|
+
// Mirrors packages/cli/src/team/sync.ts's SyncProgressEvent. Arrives as JSON
|
|
9
|
+
// over SSE `event: progress` frames — kept as a local type rather than an
|
|
10
|
+
// import to avoid apps/web depending on the cli package for a wire shape.
|
|
11
|
+
type SyncProgressEvent =
|
|
12
|
+
| { type: "phase"; phase: "usage-backfill" | "activity"; totalDays?: number }
|
|
13
|
+
| { type: "usage"; inserted: number; alreadyKnown: number }
|
|
14
|
+
| { type: "day"; day: string; index: number; total: number; outcome: "pushed" | "queued" | "dropped" }
|
|
15
|
+
| { type: "done"; pushed: number; queued: number; pushedDays: string[] }
|
|
16
|
+
| { type: "error"; message: string }
|
|
17
|
+
// Route fallback when a stdout line isn't valid JSON (shouldn't happen in
|
|
18
|
+
// practice, but the route emits it rather than dropping the line silently).
|
|
19
|
+
| { type: "log"; line: string };
|
|
20
|
+
|
|
21
|
+
type LogRow = { key: number; text: string; tone?: "warn" | "error" | "dim" };
|
|
22
|
+
|
|
23
|
+
function selectedProjectNames(projects: SyncProjectRow[], selection: SyncProjects): string[] {
|
|
24
|
+
return projects
|
|
25
|
+
.filter((p) => {
|
|
26
|
+
if (selection.excluded.includes(p.name)) return false;
|
|
27
|
+
if (selection.included.includes(p.name)) return true;
|
|
28
|
+
return selection.autoIncludeNew;
|
|
29
|
+
})
|
|
30
|
+
.map((p) => p.name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function selectionCount(projects: SyncProjectRow[], selection: SyncProjects): number {
|
|
34
|
+
return selectedProjectNames(projects, selection).length;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Client-side clock, not server time — matches "generated when the event
|
|
38
|
+
// arrives" rather than any timestamp the CLI might embed in the payload.
|
|
39
|
+
function logTimestamp(): string {
|
|
40
|
+
return new Date().toTimeString().slice(0, 8);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function OnboardingWizard({
|
|
44
|
+
teamName,
|
|
45
|
+
teamUrl,
|
|
46
|
+
serverHost,
|
|
47
|
+
projects,
|
|
48
|
+
initial,
|
|
49
|
+
setupPending,
|
|
50
|
+
}: {
|
|
51
|
+
teamName: string;
|
|
52
|
+
teamUrl: string;
|
|
53
|
+
serverHost: string;
|
|
54
|
+
projects: SyncProjectRow[];
|
|
55
|
+
initial: SyncProjects;
|
|
56
|
+
setupPending: boolean;
|
|
57
|
+
}) {
|
|
58
|
+
const [step, setStep] = useState<1 | 2 | 3>(1);
|
|
59
|
+
const [selection, setSelection] = useState<SyncProjects>(initial);
|
|
60
|
+
const [streaming, setStreaming] = useState(false);
|
|
61
|
+
const [rows, setRows] = useState<LogRow[]>([]);
|
|
62
|
+
const [pushed, setPushed] = useState<number | null>(null);
|
|
63
|
+
const [failure, setFailure] = useState<string | null>(null);
|
|
64
|
+
const [finished, setFinished] = useState(false);
|
|
65
|
+
|
|
66
|
+
async function startSync() {
|
|
67
|
+
setStreaming(true);
|
|
68
|
+
setFinished(false);
|
|
69
|
+
setFailure(null);
|
|
70
|
+
setRows([]);
|
|
71
|
+
setPushed(null);
|
|
72
|
+
let seq = 0;
|
|
73
|
+
const log = (text: string, tone?: LogRow["tone"]) =>
|
|
74
|
+
setRows((r) => [...r, { key: seq++, text: `${logTimestamp()} ${text}`, tone }]);
|
|
75
|
+
|
|
76
|
+
let res: Response;
|
|
77
|
+
try {
|
|
78
|
+
res = await fetch("/api/team/onboarding/start", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify(selection),
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
setFailure(`Request failed: ${(err as Error).message}`);
|
|
85
|
+
setStreaming(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!res.ok || !res.body) {
|
|
90
|
+
const body = await res.json().catch(() => null);
|
|
91
|
+
setFailure(body?.error ?? `Error ${res.status}`);
|
|
92
|
+
setStreaming(false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const reader = res.body.getReader();
|
|
97
|
+
const decoder = new TextDecoder();
|
|
98
|
+
let buf = "";
|
|
99
|
+
while (true) {
|
|
100
|
+
const { done, value } = await reader.read();
|
|
101
|
+
if (done) break;
|
|
102
|
+
buf += decoder.decode(value, { stream: true });
|
|
103
|
+
const frames = buf.split("\n\n");
|
|
104
|
+
buf = frames.pop() ?? "";
|
|
105
|
+
for (const frame of frames) {
|
|
106
|
+
const lines = frame.split("\n");
|
|
107
|
+
const eventLine = lines.find((l) => l.startsWith("event: "));
|
|
108
|
+
const dataLine = lines.find((l) => l.startsWith("data: "));
|
|
109
|
+
if (!dataLine) continue;
|
|
110
|
+
let data: unknown;
|
|
111
|
+
try {
|
|
112
|
+
data = JSON.parse(dataLine.slice("data: ".length));
|
|
113
|
+
} catch {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (eventLine?.slice("event: ".length) === "done") {
|
|
117
|
+
const exitCode = (data as { exitCode: number | null }).exitCode;
|
|
118
|
+
if (exitCode !== 0) {
|
|
119
|
+
setFailure(`fleetlens team sync exited with code ${exitCode ?? "unknown"}`);
|
|
120
|
+
log(`done — exit ${exitCode ?? "unknown"}`, "error");
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const ev = data as SyncProgressEvent;
|
|
125
|
+
switch (ev.type) {
|
|
126
|
+
case "phase":
|
|
127
|
+
log(ev.phase === "usage-backfill" ? "checking plan-usage history…" : `pushing ${ev.totalDays ?? "?"} day(s), newest first…`);
|
|
128
|
+
break;
|
|
129
|
+
case "usage":
|
|
130
|
+
log(
|
|
131
|
+
ev.inserted === 0 && ev.alreadyKnown === 0
|
|
132
|
+
? "no plan-usage snapshots yet — the daemon starts collecting them automatically"
|
|
133
|
+
: `usage: +${ev.inserted} snapshot(s) (${ev.alreadyKnown} already on server)`,
|
|
134
|
+
);
|
|
135
|
+
break;
|
|
136
|
+
case "day": {
|
|
137
|
+
const text =
|
|
138
|
+
ev.outcome === "pushed"
|
|
139
|
+
? `✓ ${ev.day} pushed (${ev.index}/${ev.total})`
|
|
140
|
+
: ev.outcome === "queued"
|
|
141
|
+
? `⚠ ${ev.day} queued for retry (${ev.index}/${ev.total})`
|
|
142
|
+
: `✗ ${ev.day} rejected by server (${ev.index}/${ev.total})`;
|
|
143
|
+
log(text, ev.outcome === "dropped" ? "error" : ev.outcome === "queued" ? "warn" : undefined);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "done":
|
|
147
|
+
setPushed(ev.pushed);
|
|
148
|
+
break;
|
|
149
|
+
case "error":
|
|
150
|
+
log(ev.message, "error");
|
|
151
|
+
setFailure(ev.message);
|
|
152
|
+
break;
|
|
153
|
+
case "log":
|
|
154
|
+
log(ev.line, "dim");
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
setStreaming(false);
|
|
160
|
+
setFinished(true);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<main className="mx-auto max-w-2xl space-y-6 p-6">
|
|
165
|
+
<StepDots step={step} />
|
|
166
|
+
{step === 1 && (
|
|
167
|
+
<Step1
|
|
168
|
+
teamName={teamName}
|
|
169
|
+
serverHost={serverHost}
|
|
170
|
+
setupPending={setupPending}
|
|
171
|
+
onContinue={() => setStep(2)}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
{step === 2 && (
|
|
175
|
+
<Step2
|
|
176
|
+
projects={projects}
|
|
177
|
+
selection={selection}
|
|
178
|
+
onChange={setSelection}
|
|
179
|
+
onBack={() => setStep(1)}
|
|
180
|
+
onContinue={() => setStep(3)}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
{step === 3 && (
|
|
184
|
+
<Step3
|
|
185
|
+
projects={projects}
|
|
186
|
+
selection={selection}
|
|
187
|
+
teamName={teamName}
|
|
188
|
+
serverHost={serverHost}
|
|
189
|
+
rows={rows}
|
|
190
|
+
streaming={streaming}
|
|
191
|
+
finished={finished}
|
|
192
|
+
pushed={pushed}
|
|
193
|
+
failure={failure}
|
|
194
|
+
teamUrl={teamUrl}
|
|
195
|
+
onBack={() => setStep(2)}
|
|
196
|
+
onStart={startSync}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
</main>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function StepDots({ step }: { step: 1 | 2 | 3 }) {
|
|
204
|
+
const labels: Array<[1 | 2 | 3, string]> = [
|
|
205
|
+
[1, "What happens"],
|
|
206
|
+
[2, "Choose projects"],
|
|
207
|
+
[3, "Sync"],
|
|
208
|
+
];
|
|
209
|
+
return (
|
|
210
|
+
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
211
|
+
{labels.map(([n, label], i) => (
|
|
212
|
+
<span key={n} className="flex items-center gap-2">
|
|
213
|
+
<span
|
|
214
|
+
className={`flex h-5 w-5 items-center justify-center rounded-full ${
|
|
215
|
+
n === step ? "af-step-active" : "border"
|
|
216
|
+
}`}
|
|
217
|
+
>
|
|
218
|
+
{n}
|
|
219
|
+
</span>
|
|
220
|
+
<span className={n === step ? "font-medium text-gray-900 dark:text-gray-100" : ""}>{label}</span>
|
|
221
|
+
{i < labels.length - 1 && <span className="text-gray-300 dark:text-gray-700">—</span>}
|
|
222
|
+
</span>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function Step1({
|
|
229
|
+
teamName,
|
|
230
|
+
serverHost,
|
|
231
|
+
setupPending,
|
|
232
|
+
onContinue,
|
|
233
|
+
}: {
|
|
234
|
+
teamName: string;
|
|
235
|
+
serverHost: string;
|
|
236
|
+
setupPending: boolean;
|
|
237
|
+
onContinue: () => void;
|
|
238
|
+
}) {
|
|
239
|
+
return (
|
|
240
|
+
<section className="space-y-4">
|
|
241
|
+
<h1 className="text-2xl font-semibold">You’re pairing with “{teamName}”</h1>
|
|
242
|
+
{setupPending && <p className="text-sm text-gray-500">Nothing syncs until you finish this wizard.</p>}
|
|
243
|
+
|
|
244
|
+
<div className="af-panel p-4 text-sm">
|
|
245
|
+
<p className="mb-2 font-medium">Shared with {serverHost} every 5 minutes:</p>
|
|
246
|
+
<ul className="list-inside list-disc space-y-1 text-gray-600 dark:text-gray-400">
|
|
247
|
+
<li>Daily aggregate metrics (agent time, sessions, tool calls, turns, tokens)</li>
|
|
248
|
+
<li>Per-project totals — project name, agent time, session count, and GitHub repo name when one is detected</li>
|
|
249
|
+
<li>Daily working style — working-shape mix, concurrency peak and parallel minutes, long-autonomous runs, plan-mode and brainstorm usage, tool-error count</li>
|
|
250
|
+
<li>Daily tooling and output counts — skills and sub-agents used (names + counts), PRs, commits, pushes</li>
|
|
251
|
+
<li>Session outcome mixes from AI enrichment (shipped / partial / blocked …)</li>
|
|
252
|
+
<li>Plan-utilization percentages and sync health logs</li>
|
|
253
|
+
</ul>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div className="af-panel p-4 text-sm">
|
|
257
|
+
<p className="mb-2 font-medium">Never leaves this machine:</p>
|
|
258
|
+
<ul className="list-inside list-disc space-y-1 text-gray-600 dark:text-gray-400">
|
|
259
|
+
<li>Transcripts, prompts, and code</li>
|
|
260
|
+
<li>File contents and absolute paths</li>
|
|
261
|
+
<li>Anything from projects you exclude in the next step</li>
|
|
262
|
+
</ul>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<p className="text-xs text-gray-500">Plan utilization is account-level and isn’t affected by project selection.</p>
|
|
266
|
+
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
onClick={onContinue}
|
|
270
|
+
className="af-btn-primary"
|
|
271
|
+
>
|
|
272
|
+
Continue
|
|
273
|
+
</button>
|
|
274
|
+
</section>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function Step2({
|
|
279
|
+
projects,
|
|
280
|
+
selection,
|
|
281
|
+
onChange,
|
|
282
|
+
onBack,
|
|
283
|
+
onContinue,
|
|
284
|
+
}: {
|
|
285
|
+
projects: SyncProjectRow[];
|
|
286
|
+
selection: SyncProjects;
|
|
287
|
+
onChange: (v: SyncProjects) => void;
|
|
288
|
+
onBack: () => void;
|
|
289
|
+
onContinue: () => void;
|
|
290
|
+
}) {
|
|
291
|
+
return (
|
|
292
|
+
<section className="space-y-4">
|
|
293
|
+
<h1 className="text-xl font-semibold">Choose which projects sync</h1>
|
|
294
|
+
<p className="text-sm text-gray-500">
|
|
295
|
+
Syncing {selectionCount(projects, selection)} of {projects.length} project{projects.length === 1 ? "" : "s"}
|
|
296
|
+
</p>
|
|
297
|
+
<ProjectSyncPicker projects={projects} value={selection} onChange={onChange} />
|
|
298
|
+
<div className="flex gap-3">
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
onClick={onBack}
|
|
302
|
+
className="af-btn"
|
|
303
|
+
>
|
|
304
|
+
Back
|
|
305
|
+
</button>
|
|
306
|
+
<button
|
|
307
|
+
type="button"
|
|
308
|
+
onClick={onContinue}
|
|
309
|
+
className="af-btn-primary"
|
|
310
|
+
>
|
|
311
|
+
Continue
|
|
312
|
+
</button>
|
|
313
|
+
</div>
|
|
314
|
+
</section>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function Step3({
|
|
319
|
+
projects,
|
|
320
|
+
selection,
|
|
321
|
+
teamName,
|
|
322
|
+
serverHost,
|
|
323
|
+
rows,
|
|
324
|
+
streaming,
|
|
325
|
+
finished,
|
|
326
|
+
pushed,
|
|
327
|
+
failure,
|
|
328
|
+
teamUrl,
|
|
329
|
+
onBack,
|
|
330
|
+
onStart,
|
|
331
|
+
}: {
|
|
332
|
+
projects: SyncProjectRow[];
|
|
333
|
+
selection: SyncProjects;
|
|
334
|
+
teamName: string;
|
|
335
|
+
serverHost: string;
|
|
336
|
+
rows: LogRow[];
|
|
337
|
+
streaming: boolean;
|
|
338
|
+
finished: boolean;
|
|
339
|
+
pushed: number | null;
|
|
340
|
+
failure: string | null;
|
|
341
|
+
teamUrl: string;
|
|
342
|
+
onBack: () => void;
|
|
343
|
+
onStart: () => void;
|
|
344
|
+
}) {
|
|
345
|
+
const succeeded = finished && !failure;
|
|
346
|
+
// Nothing has happened yet (not even a failed attempt) — show the summary,
|
|
347
|
+
// not the log/outcome panels below.
|
|
348
|
+
const notStarted = !streaming && !finished && !failure;
|
|
349
|
+
const logRef = useRef<HTMLDivElement>(null);
|
|
350
|
+
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
|
|
353
|
+
}, [rows]);
|
|
354
|
+
|
|
355
|
+
if (notStarted) {
|
|
356
|
+
const selected = selectedProjectNames(projects, selection);
|
|
357
|
+
const excludedCount = projects.length - selected.length;
|
|
358
|
+
const shown = selected.slice(0, 6);
|
|
359
|
+
const more = selected.length - shown.length;
|
|
360
|
+
return (
|
|
361
|
+
<section className="space-y-4">
|
|
362
|
+
<h1 className="text-xl font-semibold">
|
|
363
|
+
Syncing {selected.length} of {projects.length} project{projects.length === 1 ? "" : "s"} to “{teamName}” (
|
|
364
|
+
{serverHost})
|
|
365
|
+
</h1>
|
|
366
|
+
|
|
367
|
+
{shown.length > 0 && (
|
|
368
|
+
<div className="flex flex-wrap gap-2">
|
|
369
|
+
{shown.map((name) => (
|
|
370
|
+
<span
|
|
371
|
+
key={name}
|
|
372
|
+
className="rounded-full border border-gray-300 px-2 py-0.5 font-mono text-xs text-gray-600 dark:border-gray-700 dark:text-gray-400"
|
|
373
|
+
>
|
|
374
|
+
{name}
|
|
375
|
+
</span>
|
|
376
|
+
))}
|
|
377
|
+
{more > 0 && <span className="self-center text-xs text-gray-500">+{more} more</span>}
|
|
378
|
+
</div>
|
|
379
|
+
)}
|
|
380
|
+
|
|
381
|
+
<div className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
|
382
|
+
{excludedCount > 0 && (
|
|
383
|
+
<p>
|
|
384
|
+
{excludedCount} project{excludedCount === 1 ? " stays" : "s stay"} private on this machine.
|
|
385
|
+
</p>
|
|
386
|
+
)}
|
|
387
|
+
<p>
|
|
388
|
+
{selection.autoIncludeNew ? "New projects will sync automatically." : "New projects stay private until you add them."}
|
|
389
|
+
</p>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<ul className="list-inside list-disc space-y-1 text-xs text-gray-500">
|
|
393
|
+
<li>Aggregate metrics only — never transcripts, prompts, or file contents</li>
|
|
394
|
+
<li>Pushes run every 5 minutes from now on</li>
|
|
395
|
+
<li>You can change the selection anytime on the Team page</li>
|
|
396
|
+
</ul>
|
|
397
|
+
|
|
398
|
+
<div className="flex gap-3">
|
|
399
|
+
<button
|
|
400
|
+
type="button"
|
|
401
|
+
onClick={onBack}
|
|
402
|
+
className="af-btn"
|
|
403
|
+
>
|
|
404
|
+
Back
|
|
405
|
+
</button>
|
|
406
|
+
<button
|
|
407
|
+
type="button"
|
|
408
|
+
onClick={onStart}
|
|
409
|
+
className="af-btn-primary"
|
|
410
|
+
>
|
|
411
|
+
Start syncing
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
</section>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return (
|
|
419
|
+
<section className="space-y-4">
|
|
420
|
+
<h1 className="text-xl font-semibold">Sync your history</h1>
|
|
421
|
+
|
|
422
|
+
{rows.length > 0 && (
|
|
423
|
+
<div
|
|
424
|
+
ref={logRef}
|
|
425
|
+
className="max-h-80 space-y-0.5 overflow-y-auto rounded-lg bg-gray-950 p-3 font-mono text-xs text-gray-200"
|
|
426
|
+
>
|
|
427
|
+
{rows.map((r) => (
|
|
428
|
+
<div
|
|
429
|
+
key={r.key}
|
|
430
|
+
className={r.tone === "error" ? "text-red-400" : r.tone === "warn" ? "text-amber-400" : r.tone === "dim" ? "text-gray-500" : undefined}
|
|
431
|
+
>
|
|
432
|
+
{r.text}
|
|
433
|
+
</div>
|
|
434
|
+
))}
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
|
|
438
|
+
{succeeded && (
|
|
439
|
+
<div className="space-y-3 rounded-lg border border-green-300 bg-green-50 p-4 text-sm dark:border-green-800 dark:bg-green-950">
|
|
440
|
+
<p className="font-medium text-green-800 dark:text-green-300">
|
|
441
|
+
All synced — {pushed ?? 0} day{pushed === 1 ? "" : "s"} pushed
|
|
442
|
+
</p>
|
|
443
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
444
|
+
{/* globals.css's un-layered `a { color: inherit }` beats layered
|
|
445
|
+
utilities — button-styled anchors need the ! variants. */}
|
|
446
|
+
<a
|
|
447
|
+
href="/team"
|
|
448
|
+
className="af-btn-primary"
|
|
449
|
+
>
|
|
450
|
+
View your team sync page
|
|
451
|
+
</a>
|
|
452
|
+
<a
|
|
453
|
+
href={teamUrl}
|
|
454
|
+
target="_blank"
|
|
455
|
+
rel="noreferrer"
|
|
456
|
+
className="af-btn"
|
|
457
|
+
>
|
|
458
|
+
Open the team dashboard ↗
|
|
459
|
+
</a>
|
|
460
|
+
</div>
|
|
461
|
+
<a href="/" className="block text-xs text-gray-500! underline">
|
|
462
|
+
Back to overview
|
|
463
|
+
</a>
|
|
464
|
+
</div>
|
|
465
|
+
)}
|
|
466
|
+
|
|
467
|
+
{failure && (
|
|
468
|
+
<div className="space-y-2 rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
|
|
469
|
+
<p>{failure}</p>
|
|
470
|
+
<button
|
|
471
|
+
type="button"
|
|
472
|
+
onClick={onStart}
|
|
473
|
+
disabled={streaming}
|
|
474
|
+
className="rounded border border-red-400 px-3 py-1 text-xs disabled:opacity-50"
|
|
475
|
+
>
|
|
476
|
+
Retry
|
|
477
|
+
</button>
|
|
478
|
+
</div>
|
|
479
|
+
)}
|
|
480
|
+
|
|
481
|
+
<div className="flex gap-3">
|
|
482
|
+
<button
|
|
483
|
+
type="button"
|
|
484
|
+
onClick={onBack}
|
|
485
|
+
disabled={streaming}
|
|
486
|
+
className="rounded border border-gray-300 px-4 py-2 text-sm disabled:opacity-50 dark:border-gray-700"
|
|
487
|
+
>
|
|
488
|
+
Back
|
|
489
|
+
</button>
|
|
490
|
+
{!succeeded && (
|
|
491
|
+
<button
|
|
492
|
+
type="button"
|
|
493
|
+
onClick={onStart}
|
|
494
|
+
disabled={streaming}
|
|
495
|
+
className="af-btn-primary"
|
|
496
|
+
>
|
|
497
|
+
{streaming ? "Syncing…" : "Start syncing"}
|
|
498
|
+
</button>
|
|
499
|
+
)}
|
|
500
|
+
</div>
|
|
501
|
+
</section>
|
|
502
|
+
);
|
|
503
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { redirect } from "next/navigation";
|
|
2
|
+
import { readTeamConfig } from "@/lib/team-config";
|
|
3
|
+
import { listSyncProjectRows } from "@/lib/sync-projects-data";
|
|
4
|
+
import { OnboardingWizard } from "./onboarding-wizard";
|
|
5
|
+
|
|
6
|
+
export const dynamic = "force-dynamic";
|
|
7
|
+
|
|
8
|
+
export default async function TeamOnboardingPage() {
|
|
9
|
+
const config = readTeamConfig();
|
|
10
|
+
if (!config) redirect("/team");
|
|
11
|
+
const projects = await listSyncProjectRows();
|
|
12
|
+
return (
|
|
13
|
+
<OnboardingWizard
|
|
14
|
+
teamName={config.teamName ?? config.teamSlug}
|
|
15
|
+
teamUrl={`${config.serverUrl.replace(/\/$/, "")}/team/${config.teamSlug}`}
|
|
16
|
+
serverHost={new URL(config.serverUrl).host}
|
|
17
|
+
projects={projects}
|
|
18
|
+
initial={
|
|
19
|
+
config.syncProjects ?? {
|
|
20
|
+
autoIncludeNew: true,
|
|
21
|
+
included: projects.map((p) => p.name),
|
|
22
|
+
excluded: [],
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
setupPending={config.setupPending ?? false}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|