@a5c-ai/babysitter-observer-dashboard 5.0.1-staging.7a8768ec
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/.next/BUILD_ID +1 -0
- package/.next/app-path-routes-manifest.json +16 -0
- package/.next/build-manifest.json +23 -0
- package/.next/export-marker.json +6 -0
- package/.next/fallback-build-manifest.json +13 -0
- package/.next/images-manifest.json +68 -0
- package/.next/next-minimal-server.js.nft.json +1 -0
- package/.next/next-server.js.nft.json +1 -0
- package/.next/package.json +1 -0
- package/.next/prerender-manifest.json +114 -0
- package/.next/required-server-files.json +334 -0
- package/.next/routes-manifest.json +139 -0
- package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
- package/.next/server/app/_global-error/page/build-manifest.json +19 -0
- package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
- package/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
- package/.next/server/app/_global-error/page.js +9 -0
- package/.next/server/app/_global-error/page.js.map +5 -0
- package/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
- package/.next/server/app/_global-error.html +1 -0
- package/.next/server/app/_global-error.meta +15 -0
- package/.next/server/app/_global-error.rsc +14 -0
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
- package/.next/server/app/_global-error.segments/_full.segment.rsc +14 -0
- package/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
- package/.next/server/app/_global-error.segments/_index.segment.rsc +5 -0
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
- package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
- package/.next/server/app/_not-found/page/build-manifest.json +19 -0
- package/.next/server/app/_not-found/page/next-font-manifest.json +6 -0
- package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
- package/.next/server/app/_not-found/page.js +13 -0
- package/.next/server/app/_not-found/page.js.map +5 -0
- package/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -0
- package/.next/server/app/_not-found.html +1 -0
- package/.next/server/app/_not-found.meta +16 -0
- package/.next/server/app/_not-found.rsc +19 -0
- package/.next/server/app/_not-found.segments/_full.segment.rsc +19 -0
- package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
- package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -0
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +6 -0
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +5 -0
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
- package/.next/server/app/api/config/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/config/route/build-manifest.json +9 -0
- package/.next/server/app/api/config/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/config/route.js +8 -0
- package/.next/server/app/api/config/route.js.map +5 -0
- package/.next/server/app/api/config/route.js.nft.json +1 -0
- package/.next/server/app/api/config/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/digest/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/digest/route/build-manifest.json +9 -0
- package/.next/server/app/api/digest/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/digest/route.js +9 -0
- package/.next/server/app/api/digest/route.js.map +5 -0
- package/.next/server/app/api/digest/route.js.nft.json +1 -0
- package/.next/server/app/api/digest/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/runs/[runId]/events/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/runs/[runId]/events/route/build-manifest.json +9 -0
- package/.next/server/app/api/runs/[runId]/events/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/runs/[runId]/events/route.js +8 -0
- package/.next/server/app/api/runs/[runId]/events/route.js.map +5 -0
- package/.next/server/app/api/runs/[runId]/events/route.js.nft.json +1 -0
- package/.next/server/app/api/runs/[runId]/events/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/runs/[runId]/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/runs/[runId]/route/build-manifest.json +9 -0
- package/.next/server/app/api/runs/[runId]/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/runs/[runId]/route.js +9 -0
- package/.next/server/app/api/runs/[runId]/route.js.map +5 -0
- package/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -0
- package/.next/server/app/api/runs/[runId]/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route/build-manifest.json +9 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route.js +8 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route.js.map +5 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route.js.nft.json +1 -0
- package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/runs/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/runs/route/build-manifest.json +9 -0
- package/.next/server/app/api/runs/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/runs/route.js +9 -0
- package/.next/server/app/api/runs/route.js.map +5 -0
- package/.next/server/app/api/runs/route.js.nft.json +1 -0
- package/.next/server/app/api/runs/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/stream/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/stream/route/build-manifest.json +9 -0
- package/.next/server/app/api/stream/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/stream/route.js +8 -0
- package/.next/server/app/api/stream/route.js.map +5 -0
- package/.next/server/app/api/stream/route.js.nft.json +1 -0
- package/.next/server/app/api/stream/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/test/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/test/route/build-manifest.json +9 -0
- package/.next/server/app/api/test/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/test/route.js +6 -0
- package/.next/server/app/api/test/route.js.map +5 -0
- package/.next/server/app/api/test/route.js.nft.json +1 -0
- package/.next/server/app/api/test/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/version/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/version/route/build-manifest.json +9 -0
- package/.next/server/app/api/version/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/version/route.js +7 -0
- package/.next/server/app/api/version/route.js.map +5 -0
- package/.next/server/app/api/version/route.js.nft.json +1 -0
- package/.next/server/app/api/version/route_client-reference-manifest.js +3 -0
- package/.next/server/app/icon.svg/route/app-paths-manifest.json +3 -0
- package/.next/server/app/icon.svg/route/build-manifest.json +9 -0
- package/.next/server/app/icon.svg/route.js +7 -0
- package/.next/server/app/icon.svg/route.js.map +5 -0
- package/.next/server/app/icon.svg/route.js.nft.json +1 -0
- package/.next/server/app/icon.svg.body +20 -0
- package/.next/server/app/icon.svg.meta +1 -0
- package/.next/server/app/index.html +1 -0
- package/.next/server/app/index.meta +14 -0
- package/.next/server/app/index.rsc +21 -0
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +9 -0
- package/.next/server/app/index.segments/_full.segment.rsc +21 -0
- package/.next/server/app/index.segments/_head.segment.rsc +6 -0
- package/.next/server/app/index.segments/_index.segment.rsc +8 -0
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -0
- package/.next/server/app/page/app-paths-manifest.json +3 -0
- package/.next/server/app/page/build-manifest.json +19 -0
- package/.next/server/app/page/next-font-manifest.json +6 -0
- package/.next/server/app/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/page/server-reference-manifest.json +17 -0
- package/.next/server/app/page.js +15 -0
- package/.next/server/app/page.js.map +5 -0
- package/.next/server/app/page.js.nft.json +1 -0
- package/.next/server/app/page_client-reference-manifest.js +3 -0
- package/.next/server/app/runs/[runId]/page/app-paths-manifest.json +3 -0
- package/.next/server/app/runs/[runId]/page/build-manifest.json +19 -0
- package/.next/server/app/runs/[runId]/page/next-font-manifest.json +6 -0
- package/.next/server/app/runs/[runId]/page/react-loadable-manifest.json +22 -0
- package/.next/server/app/runs/[runId]/page/server-reference-manifest.json +17 -0
- package/.next/server/app/runs/[runId]/page.js +15 -0
- package/.next/server/app/runs/[runId]/page.js.map +5 -0
- package/.next/server/app/runs/[runId]/page.js.nft.json +1 -0
- package/.next/server/app/runs/[runId]/page_client-reference-manifest.js +3 -0
- package/.next/server/app-paths-manifest.json +16 -0
- package/.next/server/chunks/01oi_server_app_api_runs_[runId]_tasks_[effectId]_route_actions_0r72yai.js +3 -0
- package/.next/server/chunks/01oi_server_app_api_runs_[runId]_tasks_[effectId]_route_actions_0r72yai.js.map +1 -0
- package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_events_route_actions_0~msldk.js +3 -0
- package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_events_route_actions_0~msldk.js.map +1 -0
- package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_route_actions_09iz0n6.js +3 -0
- package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_route_actions_09iz0n6.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_config_route_actions_0~eypoa.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_config_route_actions_0~eypoa.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_digest_route_actions_04jj5zs.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_digest_route_actions_04jj5zs.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_runs_route_actions_0~-t-o4.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_runs_route_actions_0~-t-o4.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_stream_route_actions_0fkmv2_.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_stream_route_actions_0fkmv2_.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_test_route_actions_00ugava.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_test_route_actions_00ugava.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_version_route_actions_0~v3ojm.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_version_route_actions_0~v3ojm.js.map +1 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_icon_svg_route_actions_0yypxkm.js +3 -0
- package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_icon_svg_route_actions_0yypxkm.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0.6bt.6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0.6bt.6._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__08kwev1._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__08kwev1._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__096el.d._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__096el.d._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0_bmt4z._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0_bmt4z._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0_ln2d2._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0_ln2d2._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0al3v65._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0al3v65._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0gf516b._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0gf516b._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0kdfw4x._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0kdfw4x._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0pxt00h._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0pxt00h._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0rubnza._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0rubnza._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tn9iud._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0tn9iud._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0ws5o6i._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ws5o6i._.js.map +1 -0
- package/.next/server/chunks/[turbopack]_runtime.js +903 -0
- package/.next/server/chunks/[turbopack]_runtime.js.map +11 -0
- package/.next/server/chunks/node_modules_next_04~_e52._.js +13 -0
- package/.next/server/chunks/node_modules_next_04~_e52._.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0nyyph-.js +9 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0nyyph-.js.map +1 -0
- package/.next/server/chunks/packages_observer-dashboard_src_lib_02.vvb.._.js +3 -0
- package/.next/server/chunks/packages_observer-dashboard_src_lib_02.vvb.._.js.map +1 -0
- package/.next/server/chunks/packages_observer-dashboard_src_lib_0rqgpk0._.js +3 -0
- package/.next/server/chunks/packages_observer-dashboard_src_lib_0rqgpk0._.js.map +1 -0
- package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__global-error_page_actions_0ekoxmy.js +3 -0
- package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__global-error_page_actions_0ekoxmy.js.map +1 -0
- package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__not-found_page_actions_09b3ti3.js +3 -0
- package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__not-found_page_actions_09b3ti3.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__006f7~t._.js +4 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__006f7~t._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__02jgvbi._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__02jgvbi._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__04j8t~1._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__04j8t~1._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09c~s.0._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09c~s.0._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0cpy61n._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0cpy61n._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0cwa32j._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0cwa32j._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0fk_g0j._.js +19 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0fk_g0j._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0pddpic._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0pddpic._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0pvtneq._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0pvtneq._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0xc-2vm._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0xc-2vm._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10xgshr._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10xgshr._.js.map +1 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js +903 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +11 -0
- package/.next/server/chunks/ssr/_00yo1im._.js +3 -0
- package/.next/server/chunks/ssr/_00yo1im._.js.map +1 -0
- package/.next/server/chunks/ssr/_03sbc.o._.js +3 -0
- package/.next/server/chunks/ssr/_03sbc.o._.js.map +1 -0
- package/.next/server/chunks/ssr/_04z5ea0._.js +3 -0
- package/.next/server/chunks/ssr/_04z5ea0._.js.map +1 -0
- package/.next/server/chunks/ssr/_0gmb3g_._.js +3 -0
- package/.next/server/chunks/ssr/_0gmb3g_._.js.map +1 -0
- package/.next/server/chunks/ssr/_0okz9j8._.js +6 -0
- package/.next/server/chunks/ssr/_0okz9j8._.js.map +1 -0
- package/.next/server/chunks/ssr/_0wrbwro._.js +6 -0
- package/.next/server/chunks/ssr/_0wrbwro._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_09w7yel._.js +33 -0
- package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0avqw4q._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0avqw4q._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0h9llsw._.js +6 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0h9llsw._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0i_._k3._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0i_._k3._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0ee1czk._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0ee1czk._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0amzg6z.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0amzg6z.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dw1x0d.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dw1x0d.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0rc3ul_.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0rc3ul_.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10iomok.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10iomok.js.map +1 -0
- package/.next/server/chunks/ssr/packages_observer-dashboard_0~a2tmu._.js +3 -0
- package/.next/server/chunks/ssr/packages_observer-dashboard_0~a2tmu._.js.map +1 -0
- package/.next/server/chunks/ssr/packages_observer-dashboard_src_0f.ozc-._.js +3 -0
- package/.next/server/chunks/ssr/packages_observer-dashboard_src_0f.ozc-._.js.map +1 -0
- package/.next/server/chunks/ssr/packages_observer-dashboard_src_components_providers_tsx_03_vrrn._.js +7 -0
- package/.next/server/chunks/ssr/packages_observer-dashboard_src_components_providers_tsx_03_vrrn._.js.map +1 -0
- package/.next/server/functions-config-manifest.json +4 -0
- package/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/.next/server/middleware-build-manifest.js +23 -0
- package/.next/server/middleware-manifest.json +6 -0
- package/.next/server/next-font-manifest.js +1 -0
- package/.next/server/next-font-manifest.json +6 -0
- package/.next/server/pages/404.html +1 -0
- package/.next/server/pages/500.html +1 -0
- package/.next/server/pages-manifest.json +4 -0
- package/.next/server/prefetch-hints.json +1 -0
- package/.next/server/server-reference-manifest.js +1 -0
- package/.next/server/server-reference-manifest.json +24 -0
- package/.next/static/MvfPly4ZXHqaveDVOG1qq/_buildManifest.js +11 -0
- package/.next/static/MvfPly4ZXHqaveDVOG1qq/_clientMiddlewareManifest.js +1 -0
- package/.next/static/MvfPly4ZXHqaveDVOG1qq/_ssgManifest.js +1 -0
- package/.next/static/chunks/00c87uqn~kk2z.js +1 -0
- package/.next/static/chunks/01xlw8hd842-c.js +1 -0
- package/.next/static/chunks/02p9dpa.0oxiz.js +1 -0
- package/.next/static/chunks/034i63v_muq~d.js +1 -0
- package/.next/static/chunks/03mo-6~4pxdio.js +1 -0
- package/.next/static/chunks/03~yq9q893hmn.js +1 -0
- package/.next/static/chunks/04dm5qn77.fc2.js +1 -0
- package/.next/static/chunks/07uz2g0_38qia.js +4 -0
- package/.next/static/chunks/08nqnwvixoxnk.js +2 -0
- package/.next/static/chunks/0d3shmwh5_nmn.js +1 -0
- package/.next/static/chunks/0e_wyjw3nx.a..js +1 -0
- package/.next/static/chunks/0mstyq17cbf8-.js +1 -0
- package/.next/static/chunks/0ntuxw9.how-q.js +1 -0
- package/.next/static/chunks/0t3uzajv1qzmo.js +1 -0
- package/.next/static/chunks/0u~_nwr5-v.xp.js +1 -0
- package/.next/static/chunks/0wxcxh6eyzams.js +1 -0
- package/.next/static/chunks/0x6y7yt4kiddp.css +1 -0
- package/.next/static/chunks/0z6-vonyxr0dn.js +4 -0
- package/.next/static/chunks/0ze4gu236oq96.js +31 -0
- package/.next/static/chunks/0zwozael9msy1.js +1 -0
- package/.next/static/chunks/10qd__0r7a~.l.js +1 -0
- package/.next/static/chunks/11jh_u1ynmatg.js +1 -0
- package/.next/static/chunks/13xer3cb9shu-.js +5 -0
- package/.next/static/chunks/142zlnch_xmgd.js +1 -0
- package/.next/static/chunks/144kcri75qczu.js +1 -0
- package/.next/static/chunks/turbopack-0-ww6fe7b37qt.js +1 -0
- package/.next/static/media/icon.08ljfy7xai2x_.svg +20 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/next.config.mjs +25 -0
- package/package.json +104 -0
- package/postcss.config.mjs +8 -0
- package/src/app/actions/__tests__/approve-breakpoint.test.ts +246 -0
- package/src/app/actions/approve-breakpoint.ts +145 -0
- package/src/app/api/config/route.ts +137 -0
- package/src/app/api/digest/route.ts +45 -0
- package/src/app/api/runs/[runId]/events/route.ts +56 -0
- package/src/app/api/runs/[runId]/route.ts +84 -0
- package/src/app/api/runs/[runId]/tasks/[effectId]/route.ts +44 -0
- package/src/app/api/runs/route.ts +48 -0
- package/src/app/api/stream/route.ts +136 -0
- package/src/app/api/test/route.ts +1 -0
- package/src/app/api/version/route.ts +57 -0
- package/src/app/globals.css +555 -0
- package/src/app/icon.svg +20 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/not-found.tsx +16 -0
- package/src/app/page.tsx +120 -0
- package/src/app/runs/[runId]/page.tsx +279 -0
- package/src/cli.ts +271 -0
- package/src/components/breakpoint/__tests__/breakpoint-approval.test.tsx +212 -0
- package/src/components/breakpoint/__tests__/breakpoint-panel.test.tsx +130 -0
- package/src/components/breakpoint/__tests__/file-preview.test.tsx +313 -0
- package/src/components/breakpoint/breakpoint-approval.tsx +138 -0
- package/src/components/breakpoint/breakpoint-panel.tsx +95 -0
- package/src/components/breakpoint/file-preview.tsx +215 -0
- package/src/components/dashboard/.gitkeep +0 -0
- package/src/components/dashboard/__tests__/breakpoint-banner.test.tsx +177 -0
- package/src/components/dashboard/__tests__/catch-up-banner.test.tsx +141 -0
- package/src/components/dashboard/__tests__/executive-summary-banner.test.tsx +164 -0
- package/src/components/dashboard/__tests__/kpi-grid.test.tsx +101 -0
- package/src/components/dashboard/__tests__/pagination-controls.test.tsx +125 -0
- package/src/components/dashboard/__tests__/project-accordion.test.tsx +97 -0
- package/src/components/dashboard/__tests__/project-list-view.test.tsx +174 -0
- package/src/components/dashboard/__tests__/project-search-input.test.tsx +110 -0
- package/src/components/dashboard/__tests__/project-section-header.test.tsx +91 -0
- package/src/components/dashboard/__tests__/project-section.test.tsx +151 -0
- package/src/components/dashboard/__tests__/run-card.test.tsx +164 -0
- package/src/components/dashboard/__tests__/run-filter-bar.test.tsx +109 -0
- package/src/components/dashboard/__tests__/run-list.test.tsx +123 -0
- package/src/components/dashboard/__tests__/search-filter.test.tsx +150 -0
- package/src/components/dashboard/__tests__/virtualized-run-list.test.tsx +179 -0
- package/src/components/dashboard/breakpoint-banner.tsx +301 -0
- package/src/components/dashboard/catch-up-banner.tsx +88 -0
- package/src/components/dashboard/executive-summary-banner.tsx +174 -0
- package/src/components/dashboard/global-search.tsx +323 -0
- package/src/components/dashboard/kpi-grid.tsx +140 -0
- package/src/components/dashboard/pagination-controls.tsx +100 -0
- package/src/components/dashboard/project-accordion.tsx +72 -0
- package/src/components/dashboard/project-health-card.tsx +536 -0
- package/src/components/dashboard/project-list-view.tsx +246 -0
- package/src/components/dashboard/project-search-input.tsx +41 -0
- package/src/components/dashboard/project-section-header.tsx +73 -0
- package/src/components/dashboard/project-section.tsx +89 -0
- package/src/components/dashboard/run-card.tsx +218 -0
- package/src/components/dashboard/run-filter-bar.tsx +100 -0
- package/src/components/dashboard/run-list.tsx +77 -0
- package/src/components/dashboard/search-filter.tsx +69 -0
- package/src/components/dashboard/virtualized-run-list.tsx +130 -0
- package/src/components/details/.gitkeep +0 -0
- package/src/components/details/__tests__/agent-panel.test.tsx +236 -0
- package/src/components/details/__tests__/json-tree.test.tsx +347 -0
- package/src/components/details/__tests__/log-viewer.test.tsx +168 -0
- package/src/components/details/__tests__/task-detail.test.tsx +212 -0
- package/src/components/details/__tests__/timing-panel.test.tsx +271 -0
- package/src/components/details/agent-panel.tsx +234 -0
- package/src/components/details/json-tree/categorize.ts +131 -0
- package/src/components/details/json-tree/index.tsx +120 -0
- package/src/components/details/json-tree/json-node.tsx +223 -0
- package/src/components/details/json-tree/smart-summary.tsx +596 -0
- package/src/components/details/json-tree/tree-controls.tsx +47 -0
- package/src/components/details/json-tree.tsx +9 -0
- package/src/components/details/log-viewer.tsx +140 -0
- package/src/components/details/task-detail.tsx +114 -0
- package/src/components/details/timing-panel.tsx +247 -0
- package/src/components/events/.gitkeep +0 -0
- package/src/components/events/__tests__/event-item.test.tsx +211 -0
- package/src/components/events/__tests__/event-stream.test.tsx +225 -0
- package/src/components/events/event-item.tsx +121 -0
- package/src/components/events/event-stream.tsx +260 -0
- package/src/components/notifications/.gitkeep +0 -0
- package/src/components/notifications/__tests__/notification-panel.test.tsx +287 -0
- package/src/components/notifications/__tests__/notification-provider.test.tsx +585 -0
- package/src/components/notifications/__tests__/toast-stack.test.tsx +217 -0
- package/src/components/notifications/notification-panel.tsx +124 -0
- package/src/components/notifications/notification-provider.tsx +175 -0
- package/src/components/notifications/toast-stack.tsx +75 -0
- package/src/components/pipeline/.gitkeep +0 -0
- package/src/components/pipeline/__tests__/parallel-group.test.tsx +88 -0
- package/src/components/pipeline/__tests__/pipeline-view.test.tsx +345 -0
- package/src/components/pipeline/__tests__/step-card.test.tsx +330 -0
- package/src/components/pipeline/parallel-group.tsx +39 -0
- package/src/components/pipeline/pipeline-view.tsx +197 -0
- package/src/components/pipeline/step-card.tsx +166 -0
- package/src/components/providers/event-stream-provider.tsx +29 -0
- package/src/components/providers.tsx +24 -0
- package/src/components/shared/.gitkeep +0 -0
- package/src/components/shared/__tests__/empty-state.test.tsx +49 -0
- package/src/components/shared/__tests__/friendly-id.test.tsx +47 -0
- package/src/components/shared/__tests__/kbd.test.tsx +45 -0
- package/src/components/shared/__tests__/kind-badge.test.tsx +71 -0
- package/src/components/shared/__tests__/metrics-row.test.tsx +74 -0
- package/src/components/shared/__tests__/outcome-banner.test.tsx +71 -0
- package/src/components/shared/__tests__/progress-bar.test.tsx +89 -0
- package/src/components/shared/__tests__/session-pill.test.tsx +62 -0
- package/src/components/shared/__tests__/settings-modal.test.tsx +201 -0
- package/src/components/shared/__tests__/shortcuts-help.test.tsx +103 -0
- package/src/components/shared/__tests__/status-badge.test.tsx +98 -0
- package/src/components/shared/__tests__/theme-provider.test.tsx +100 -0
- package/src/components/shared/__tests__/truncated-id.test.tsx +53 -0
- package/src/components/shared/app-footer.tsx +80 -0
- package/src/components/shared/app-header.tsx +160 -0
- package/src/components/shared/empty-state.tsx +18 -0
- package/src/components/shared/error-boundary.tsx +81 -0
- package/src/components/shared/friendly-id.tsx +48 -0
- package/src/components/shared/kbd.tsx +15 -0
- package/src/components/shared/kind-badge.tsx +51 -0
- package/src/components/shared/metrics-row.tsx +106 -0
- package/src/components/shared/outcome-banner.tsx +56 -0
- package/src/components/shared/progress-bar.tsx +42 -0
- package/src/components/shared/session-pill.tsx +69 -0
- package/src/components/shared/settings-modal.tsx +509 -0
- package/src/components/shared/shortcuts-help.tsx +113 -0
- package/src/components/shared/status-badge.tsx +110 -0
- package/src/components/shared/theme-provider.tsx +46 -0
- package/src/components/shared/truncated-id.tsx +51 -0
- package/src/components/ui/.gitkeep +0 -0
- package/src/components/ui/__tests__/accordion.test.tsx +96 -0
- package/src/components/ui/__tests__/badge.test.tsx +69 -0
- package/src/components/ui/__tests__/button.test.tsx +113 -0
- package/src/components/ui/__tests__/tabs.test.tsx +75 -0
- package/src/components/ui/__tests__/tooltip.test.tsx +90 -0
- package/src/components/ui/accordion.tsx +61 -0
- package/src/components/ui/badge.tsx +25 -0
- package/src/components/ui/button.tsx +40 -0
- package/src/components/ui/card.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +35 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/tooltip.tsx +37 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/__tests__/use-animated-number.test.ts +184 -0
- package/src/hooks/__tests__/use-batched-updates.test.ts +315 -0
- package/src/hooks/__tests__/use-event-stream.test.ts +243 -0
- package/src/hooks/__tests__/use-keyboard.test.ts +217 -0
- package/src/hooks/__tests__/use-notifications.test.ts +230 -0
- package/src/hooks/__tests__/use-polling.test.ts +274 -0
- package/src/hooks/__tests__/use-project-runs.test.ts +163 -0
- package/src/hooks/__tests__/use-projects.test.ts +248 -0
- package/src/hooks/__tests__/use-run-dashboard.test.ts +168 -0
- package/src/hooks/__tests__/use-run-detail.test.ts +273 -0
- package/src/hooks/__tests__/use-smart-polling.test.ts +305 -0
- package/src/hooks/use-animated-number.ts +87 -0
- package/src/hooks/use-batched-updates.ts +150 -0
- package/src/hooks/use-event-stream.ts +150 -0
- package/src/hooks/use-keyboard.ts +45 -0
- package/src/hooks/use-notifications.ts +82 -0
- package/src/hooks/use-persisted-state.ts +60 -0
- package/src/hooks/use-polling.ts +60 -0
- package/src/hooks/use-project-runs.ts +51 -0
- package/src/hooks/use-projects.ts +26 -0
- package/src/hooks/use-run-dashboard.ts +207 -0
- package/src/hooks/use-run-detail.ts +77 -0
- package/src/hooks/use-smart-polling.ts +144 -0
- package/src/lib/.gitkeep +0 -0
- package/src/lib/__tests__/cn.test.ts +69 -0
- package/src/lib/__tests__/config-loader.test.ts +210 -0
- package/src/lib/__tests__/config.test.ts +561 -0
- package/src/lib/__tests__/error-handler.test.ts +143 -0
- package/src/lib/__tests__/fetcher.test.ts +517 -0
- package/src/lib/__tests__/global-registry.test.ts +214 -0
- package/src/lib/__tests__/parser.test.ts +1532 -0
- package/src/lib/__tests__/path-resolver.test.ts +112 -0
- package/src/lib/__tests__/run-cache.test.ts +591 -0
- package/src/lib/__tests__/server-init.test.ts +512 -0
- package/src/lib/__tests__/source-discovery.test.ts +246 -0
- package/src/lib/__tests__/utils.test.ts +160 -0
- package/src/lib/__tests__/watcher.test.ts +227 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/config-loader.ts +195 -0
- package/src/lib/config.ts +20 -0
- package/src/lib/error-handler.ts +76 -0
- package/src/lib/fetcher.ts +394 -0
- package/src/lib/global-registry.ts +117 -0
- package/src/lib/parser.ts +794 -0
- package/src/lib/path-resolver.ts +16 -0
- package/src/lib/run-cache.ts +404 -0
- package/src/lib/server-init.ts +226 -0
- package/src/lib/services/__tests__/run-query-service.test.ts +819 -0
- package/src/lib/services/run-query-service.ts +286 -0
- package/src/lib/source-discovery.ts +216 -0
- package/src/lib/utils.ts +103 -0
- package/src/lib/watcher.ts +265 -0
- package/src/test/fixtures.ts +269 -0
- package/src/test/mocks/handlers.ts +110 -0
- package/src/test/mocks/server.ts +17 -0
- package/src/test/setup.ts +200 -0
- package/src/test/test-utils.tsx +36 -0
- package/src/types/.gitkeep +0 -0
- package/src/types/breakpoint.ts +17 -0
- package/src/types/index.ts +214 -0
- package/tsconfig.json +50 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { render, screen } from '@/test/test-utils';
|
|
4
|
+
import { StatusBadge } from '../status-badge';
|
|
5
|
+
|
|
6
|
+
describe('StatusBadge', () => {
|
|
7
|
+
it('renders without crashing', () => {
|
|
8
|
+
render(<StatusBadge status="completed" />);
|
|
9
|
+
expect(screen.getByText('Completed')).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders "Completed" label for completed status', () => {
|
|
13
|
+
render(<StatusBadge status="completed" />);
|
|
14
|
+
expect(screen.getByText('Completed')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders "Done" label for resolved status', () => {
|
|
18
|
+
render(<StatusBadge status="resolved" />);
|
|
19
|
+
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders "OK" label for ok status', () => {
|
|
23
|
+
render(<StatusBadge status="ok" />);
|
|
24
|
+
expect(screen.getByText('OK')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders "Failed" label for failed status', () => {
|
|
28
|
+
render(<StatusBadge status="failed" />);
|
|
29
|
+
expect(screen.getByText('Failed')).toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders "Error" label for error status', () => {
|
|
33
|
+
render(<StatusBadge status="error" />);
|
|
34
|
+
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders "Waiting" label for waiting status', () => {
|
|
38
|
+
render(<StatusBadge status="waiting" />);
|
|
39
|
+
expect(screen.getByText('Waiting')).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders "Needs Approval" label for breakpoint_awaiting status', () => {
|
|
43
|
+
render(<StatusBadge status="breakpoint_awaiting" />);
|
|
44
|
+
expect(screen.getByText('Needs Approval')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders "Running" label for requested status', () => {
|
|
48
|
+
render(<StatusBadge status="requested" />);
|
|
49
|
+
expect(screen.getByText('Running')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders "Pending" label for pending status', () => {
|
|
53
|
+
render(<StatusBadge status="pending" />);
|
|
54
|
+
expect(screen.getByText('Pending')).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('falls back to pending config for unknown status', () => {
|
|
58
|
+
render(<StatusBadge status="unknown_status" />);
|
|
59
|
+
expect(screen.getByText('Pending')).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('applies custom className', () => {
|
|
63
|
+
const { container } = render(<StatusBadge status="completed" className="my-custom" />);
|
|
64
|
+
const badge = container.firstChild as HTMLElement;
|
|
65
|
+
expect(badge.className).toContain('my-custom');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders an icon alongside the label', () => {
|
|
69
|
+
const { container } = render(<StatusBadge status="completed" />);
|
|
70
|
+
// The Badge wrapper should contain both the SVG icon and the text
|
|
71
|
+
const svg = container.querySelector('svg');
|
|
72
|
+
expect(svg).toBeInTheDocument();
|
|
73
|
+
expect(screen.getByText('Completed')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('renders "Approval Needed" when status is waiting and waitingKind is breakpoint', () => {
|
|
77
|
+
render(<StatusBadge status="waiting" waitingKind="breakpoint" />);
|
|
78
|
+
expect(screen.getByText('Approval Needed')).toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('renders "Working" when status is waiting and waitingKind is task', () => {
|
|
82
|
+
render(<StatusBadge status="waiting" waitingKind="task" />);
|
|
83
|
+
expect(screen.getByText('Working')).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('renders with stale styling when isStale is true', () => {
|
|
87
|
+
const { container } = render(<StatusBadge status="waiting" isStale={true} />);
|
|
88
|
+
const badge = container.firstChild as HTMLElement;
|
|
89
|
+
expect(badge.className).toContain('opacity-60');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('renders normal waiting when waitingKind not provided', () => {
|
|
93
|
+
render(<StatusBadge status="waiting" />);
|
|
94
|
+
expect(screen.getByText('Waiting')).toBeInTheDocument();
|
|
95
|
+
expect(screen.queryByText('Approval Needed')).not.toBeInTheDocument();
|
|
96
|
+
expect(screen.queryByText('Working')).not.toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
3
|
+
import { render, screen, fireEvent } from '@/test/test-utils';
|
|
4
|
+
import { ThemeProvider, useTheme } from '../theme-provider';
|
|
5
|
+
|
|
6
|
+
function ThemeConsumer() {
|
|
7
|
+
const { theme, toggle } = useTheme();
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
<span data-testid="theme-value">{theme}</span>
|
|
11
|
+
<button onClick={toggle}>Toggle</button>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('ThemeProvider', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Reset DOM state between tests since ThemeProvider reads from the DOM
|
|
19
|
+
document.documentElement.removeAttribute('data-theme');
|
|
20
|
+
document.documentElement.className = '';
|
|
21
|
+
localStorage.removeItem('observer-theme');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders children', () => {
|
|
25
|
+
render(
|
|
26
|
+
<ThemeProvider>
|
|
27
|
+
<span>child content</span>
|
|
28
|
+
</ThemeProvider>,
|
|
29
|
+
);
|
|
30
|
+
expect(screen.getByText('child content')).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('provides default dark theme', () => {
|
|
34
|
+
render(
|
|
35
|
+
<ThemeProvider>
|
|
36
|
+
<ThemeConsumer />
|
|
37
|
+
</ThemeProvider>,
|
|
38
|
+
);
|
|
39
|
+
expect(screen.getByTestId('theme-value').textContent).toBe('dark');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('toggles from dark to light', () => {
|
|
43
|
+
render(
|
|
44
|
+
<ThemeProvider>
|
|
45
|
+
<ThemeConsumer />
|
|
46
|
+
</ThemeProvider>,
|
|
47
|
+
);
|
|
48
|
+
fireEvent.click(screen.getByText('Toggle'));
|
|
49
|
+
expect(screen.getByTestId('theme-value').textContent).toBe('light');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('toggles from light back to dark', () => {
|
|
53
|
+
render(
|
|
54
|
+
<ThemeProvider>
|
|
55
|
+
<ThemeConsumer />
|
|
56
|
+
</ThemeProvider>,
|
|
57
|
+
);
|
|
58
|
+
fireEvent.click(screen.getByText('Toggle'));
|
|
59
|
+
expect(screen.getByTestId('theme-value').textContent).toBe('light');
|
|
60
|
+
fireEvent.click(screen.getByText('Toggle'));
|
|
61
|
+
expect(screen.getByTestId('theme-value').textContent).toBe('dark');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('sets data-theme attribute on document element when toggling', () => {
|
|
65
|
+
render(
|
|
66
|
+
<ThemeProvider>
|
|
67
|
+
<ThemeConsumer />
|
|
68
|
+
</ThemeProvider>,
|
|
69
|
+
);
|
|
70
|
+
fireEvent.click(screen.getByText('Toggle'));
|
|
71
|
+
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('sets className on document element when toggling', () => {
|
|
75
|
+
render(
|
|
76
|
+
<ThemeProvider>
|
|
77
|
+
<ThemeConsumer />
|
|
78
|
+
</ThemeProvider>,
|
|
79
|
+
);
|
|
80
|
+
fireEvent.click(screen.getByText('Toggle'));
|
|
81
|
+
expect(document.documentElement.className).toBe('light');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('stores theme in localStorage when toggling', () => {
|
|
85
|
+
render(
|
|
86
|
+
<ThemeProvider>
|
|
87
|
+
<ThemeConsumer />
|
|
88
|
+
</ThemeProvider>,
|
|
89
|
+
);
|
|
90
|
+
fireEvent.click(screen.getByText('Toggle'));
|
|
91
|
+
expect(localStorage.getItem('observer-theme')).toBe('light');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('useTheme without provider', () => {
|
|
96
|
+
it('returns default values when used outside ThemeProvider', () => {
|
|
97
|
+
render(<ThemeConsumer />);
|
|
98
|
+
expect(screen.getByTestId('theme-value').textContent).toBe('dark');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render, screen } from '@/test/test-utils';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { TruncatedId } from '../truncated-id';
|
|
6
|
+
|
|
7
|
+
describe('TruncatedId', () => {
|
|
8
|
+
it('renders without crashing', () => {
|
|
9
|
+
render(<TruncatedId id="abc123def456" />);
|
|
10
|
+
// formatShortId("abc123def456", 4) => "...f456"
|
|
11
|
+
expect(screen.getByText('...f456')).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('renders truncated ID with default 4 chars', () => {
|
|
15
|
+
render(<TruncatedId id="long-id-string-xyz" />);
|
|
16
|
+
expect(screen.getByText('...-xyz')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders truncated ID with custom chars count', () => {
|
|
20
|
+
render(<TruncatedId id="abcdefghij" chars={6} />);
|
|
21
|
+
// formatShortId("abcdefghij", 6) => "...efghij"
|
|
22
|
+
expect(screen.getByText('...efghij')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders full ID when shorter than chars', () => {
|
|
26
|
+
render(<TruncatedId id="ab" chars={4} />);
|
|
27
|
+
expect(screen.getByText('ab')).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('copies full ID to clipboard on click', async () => {
|
|
31
|
+
const user = userEvent.setup();
|
|
32
|
+
const writeTextSpy = vi.spyOn(navigator.clipboard, 'writeText');
|
|
33
|
+
render(<TruncatedId id="full-id-to-copy" />);
|
|
34
|
+
const el = screen.getByText('...copy');
|
|
35
|
+
await user.click(el);
|
|
36
|
+
expect(writeTextSpy).toHaveBeenCalledWith('full-id-to-copy');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('applies copied class after click', async () => {
|
|
40
|
+
const user = userEvent.setup();
|
|
41
|
+
render(<TruncatedId id="some-test-id" />);
|
|
42
|
+
const el = screen.getByText('...t-id');
|
|
43
|
+
await user.click(el);
|
|
44
|
+
// After copying, the element gets 'text-primary' class to indicate copied state
|
|
45
|
+
expect(el.className).toContain('text-primary');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('applies custom className', () => {
|
|
49
|
+
const { container } = render(<TruncatedId id="test-id" className="custom-class" />);
|
|
50
|
+
const span = container.querySelector('span');
|
|
51
|
+
expect(span?.className).toContain('custom-class');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Eye, Github, ExternalLink } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
export function AppFooter() {
|
|
6
|
+
const [versions, setVersions] = useState({
|
|
7
|
+
app: process.env.NEXT_PUBLIC_APP_VERSION || "…",
|
|
8
|
+
babysitter: process.env.NEXT_PUBLIC_BABYSITTER_VERSION || "…",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
fetch("/api/version")
|
|
13
|
+
.then((r) => r.json())
|
|
14
|
+
.then((data) => {
|
|
15
|
+
if (data.app || data.babysitter) {
|
|
16
|
+
setVersions((prev) => ({
|
|
17
|
+
app: data.app || prev.app,
|
|
18
|
+
babysitter: data.babysitter || prev.babysitter,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.catch(() => {
|
|
23
|
+
// Keep build-time fallback values
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<footer className="border-t border-border bg-background/80 backdrop-blur-sm">
|
|
29
|
+
<div className="mx-auto max-w-[1600px] px-6 py-3 flex flex-wrap items-center justify-between gap-3 text-xs text-foreground-muted">
|
|
30
|
+
<div className="flex items-center gap-3 flex-wrap">
|
|
31
|
+
<Eye className="h-3.5 w-3.5 text-primary/40" />
|
|
32
|
+
<span className="font-medium">
|
|
33
|
+
Babysitter Observer{" "}
|
|
34
|
+
<span className="rounded-full bg-primary/10 border border-primary/20 px-1.5 py-px text-[10px] font-medium text-primary tabular-nums">
|
|
35
|
+
v{versions.app}
|
|
36
|
+
</span>
|
|
37
|
+
</span>
|
|
38
|
+
|
|
39
|
+
<span className="text-foreground-muted/30 select-none">·</span>
|
|
40
|
+
|
|
41
|
+
<span className="text-xs font-normal text-foreground-muted">
|
|
42
|
+
Babysitter{" "}
|
|
43
|
+
<span className="rounded-full bg-muted/50 border border-border px-1.5 py-px text-[10px] font-normal text-foreground-muted tabular-nums">
|
|
44
|
+
v{versions.babysitter}
|
|
45
|
+
</span>
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="flex items-center gap-4">
|
|
49
|
+
<a
|
|
50
|
+
href="https://github.com/a5c-ai/babysitter/tree/main/packages/observer-dashboard#readme"
|
|
51
|
+
target="_blank"
|
|
52
|
+
rel="noopener noreferrer"
|
|
53
|
+
className="inline-flex items-center gap-1 hover:text-foreground-secondary transition-colors"
|
|
54
|
+
>
|
|
55
|
+
<Github className="h-3 w-3" />
|
|
56
|
+
Docs
|
|
57
|
+
</a>
|
|
58
|
+
<a
|
|
59
|
+
href="https://github.com/a5c-ai/babysitter"
|
|
60
|
+
target="_blank"
|
|
61
|
+
rel="noopener noreferrer"
|
|
62
|
+
className="inline-flex items-center gap-1 hover:text-foreground-secondary transition-colors"
|
|
63
|
+
>
|
|
64
|
+
<Github className="h-3 w-3" />
|
|
65
|
+
Babysitter
|
|
66
|
+
</a>
|
|
67
|
+
<a
|
|
68
|
+
href="https://www.a5c.ai/"
|
|
69
|
+
target="_blank"
|
|
70
|
+
rel="noopener noreferrer"
|
|
71
|
+
className="inline-flex items-center gap-1 hover:text-primary transition-colors"
|
|
72
|
+
>
|
|
73
|
+
a5c.ai
|
|
74
|
+
<ExternalLink className="h-3 w-3" />
|
|
75
|
+
</a>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</footer>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useCallback, useEffect } from "react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useTheme } from "@/components/shared/theme-provider";
|
|
5
|
+
import { useEventStream } from "@/hooks/use-event-stream";
|
|
6
|
+
import { useNotificationContext } from "@/components/notifications/notification-provider";
|
|
7
|
+
import { useKeyboard } from "@/hooks/use-keyboard";
|
|
8
|
+
import { NotificationPanel } from "@/components/notifications/notification-panel";
|
|
9
|
+
import { SettingsModal } from "@/components/shared/settings-modal";
|
|
10
|
+
import { cn } from "@/lib/cn";
|
|
11
|
+
import {
|
|
12
|
+
Eye,
|
|
13
|
+
Sun,
|
|
14
|
+
Moon,
|
|
15
|
+
Settings,
|
|
16
|
+
Bell,
|
|
17
|
+
HelpCircle,
|
|
18
|
+
Wifi,
|
|
19
|
+
WifiOff,
|
|
20
|
+
Github,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
|
|
23
|
+
export function AppHeader() {
|
|
24
|
+
const { theme, toggle: toggleTheme } = useTheme();
|
|
25
|
+
const { connected: sseConnected } = useEventStream();
|
|
26
|
+
const { notifications, dismiss } = useNotificationContext();
|
|
27
|
+
const [showNotificationPanel, setShowNotificationPanel] = useState(false);
|
|
28
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
29
|
+
|
|
30
|
+
const toggleNotificationPanel = useCallback(() => {
|
|
31
|
+
setShowNotificationPanel((v) => !v);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
useKeyboard([
|
|
35
|
+
{ key: "n", action: toggleNotificationPanel, description: "Toggle notifications" },
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Allow external components to open the settings modal via custom event
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const handler = () => setShowSettings(true);
|
|
41
|
+
window.addEventListener("open-settings", handler);
|
|
42
|
+
return () => window.removeEventListener("open-settings", handler);
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
<a
|
|
49
|
+
href="#main-content"
|
|
50
|
+
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[100] focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-md"
|
|
51
|
+
>
|
|
52
|
+
Skip to main content
|
|
53
|
+
</a>
|
|
54
|
+
<header className="sticky top-0 z-30 border-b border-border bg-background/80 backdrop-blur-md">
|
|
55
|
+
<div className="mx-auto max-w-[1600px] px-6 py-3 flex items-center gap-3">
|
|
56
|
+
<Eye className="h-5 w-5 text-primary" />
|
|
57
|
+
<h1 className="text-base font-semibold">
|
|
58
|
+
<Link href="/" className="text-foreground hover:text-primary transition-colors">
|
|
59
|
+
Babysitter Observer
|
|
60
|
+
</Link>
|
|
61
|
+
</h1>
|
|
62
|
+
<a
|
|
63
|
+
href="https://www.a5c.ai/"
|
|
64
|
+
target="_blank"
|
|
65
|
+
rel="noopener noreferrer"
|
|
66
|
+
className="text-xs leading-tight font-medium text-primary/60 hover:text-primary transition-colors hidden sm:block"
|
|
67
|
+
>
|
|
68
|
+
a5c.ai
|
|
69
|
+
</a>
|
|
70
|
+
<a
|
|
71
|
+
href="https://github.com/a5c-ai/babysitter"
|
|
72
|
+
target="_blank"
|
|
73
|
+
rel="noopener noreferrer"
|
|
74
|
+
className="inline-flex items-center gap-1 text-xs text-foreground-muted hover:text-foreground-secondary transition-colors hidden sm:block"
|
|
75
|
+
title="Babysitter on GitHub"
|
|
76
|
+
>
|
|
77
|
+
<Github className="h-3.5 w-3.5" />
|
|
78
|
+
</a>
|
|
79
|
+
<span className="text-xs text-foreground-muted hidden md:block">
|
|
80
|
+
Real-time orchestration dashboard
|
|
81
|
+
</span>
|
|
82
|
+
<div className="ml-auto flex items-center gap-1.5">
|
|
83
|
+
{/* SSE connection status chip */}
|
|
84
|
+
<span
|
|
85
|
+
data-testid="sse-status"
|
|
86
|
+
role="status"
|
|
87
|
+
aria-live="polite"
|
|
88
|
+
className={cn(
|
|
89
|
+
"inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium mr-1 transition-all",
|
|
90
|
+
sseConnected
|
|
91
|
+
? "bg-success/10 text-success border border-success/20"
|
|
92
|
+
: "bg-error/10 text-error border border-error/20"
|
|
93
|
+
)}
|
|
94
|
+
title={sseConnected ? "Live updates connected" : "Live updates disconnected"}
|
|
95
|
+
>
|
|
96
|
+
{sseConnected ? (
|
|
97
|
+
<Wifi className="h-3 w-3" />
|
|
98
|
+
) : (
|
|
99
|
+
<WifiOff className="h-3 w-3" />
|
|
100
|
+
)}
|
|
101
|
+
<span className="hidden sm:inline">{sseConnected ? "Live" : "Offline"}</span>
|
|
102
|
+
</span>
|
|
103
|
+
{/* Notification bell */}
|
|
104
|
+
<button
|
|
105
|
+
data-testid="notification-bell"
|
|
106
|
+
onClick={toggleNotificationPanel}
|
|
107
|
+
className="relative rounded-md p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-foreground-muted hover:text-foreground-secondary hover:bg-background-secondary transition-colors"
|
|
108
|
+
title="Notifications"
|
|
109
|
+
aria-label={`Notifications${notifications.length > 0 ? ` (${notifications.length} unread)` : ""}`}
|
|
110
|
+
>
|
|
111
|
+
<Bell className="h-4 w-4" />
|
|
112
|
+
{notifications.length > 0 && (
|
|
113
|
+
<span data-testid="notification-badge" className="absolute -top-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground tabular-nums">
|
|
114
|
+
{notifications.length > 9 ? "9+" : notifications.length}
|
|
115
|
+
</span>
|
|
116
|
+
)}
|
|
117
|
+
</button>
|
|
118
|
+
{/* Keyboard shortcuts help */}
|
|
119
|
+
<button
|
|
120
|
+
onClick={() => window.dispatchEvent(new CustomEvent("open-shortcuts-help"))}
|
|
121
|
+
className="rounded-md p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-foreground-muted hover:text-foreground-secondary hover:bg-background-secondary transition-colors"
|
|
122
|
+
title="Keyboard shortcuts"
|
|
123
|
+
aria-label="Keyboard shortcuts"
|
|
124
|
+
>
|
|
125
|
+
<HelpCircle className="h-4 w-4" />
|
|
126
|
+
</button>
|
|
127
|
+
<button
|
|
128
|
+
onClick={() => setShowSettings(true)}
|
|
129
|
+
className="rounded-md p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-foreground-muted hover:text-foreground-secondary hover:bg-background-secondary transition-colors"
|
|
130
|
+
title="Settings"
|
|
131
|
+
aria-label="Settings"
|
|
132
|
+
>
|
|
133
|
+
<Settings className="h-4 w-4" />
|
|
134
|
+
</button>
|
|
135
|
+
<button
|
|
136
|
+
data-testid="theme-toggle"
|
|
137
|
+
onClick={toggleTheme}
|
|
138
|
+
className="rounded-md p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-foreground-muted hover:text-foreground-secondary hover:bg-background-secondary transition-colors"
|
|
139
|
+
title={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
|
|
140
|
+
aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
|
|
141
|
+
>
|
|
142
|
+
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</header>
|
|
147
|
+
|
|
148
|
+
{/* Notification Panel */}
|
|
149
|
+
<NotificationPanel
|
|
150
|
+
open={showNotificationPanel}
|
|
151
|
+
notifications={notifications}
|
|
152
|
+
onDismiss={dismiss}
|
|
153
|
+
onClose={() => setShowNotificationPanel(false)}
|
|
154
|
+
/>
|
|
155
|
+
|
|
156
|
+
{/* Settings Modal */}
|
|
157
|
+
<SettingsModal open={showSettings} onClose={() => setShowSettings(false)} />
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cn } from "@/lib/cn";
|
|
2
|
+
import { Inbox } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface EmptyStateProps {
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function EmptyState({ title = "No runs found", description = "Start a babysitter run to see it here.", className }: EmptyStateProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className={cn("flex flex-col items-center justify-center py-16 text-center", className)}>
|
|
13
|
+
<Inbox className="h-12 w-12 text-foreground-muted/30 mb-4" />
|
|
14
|
+
<h3 className="text-sm font-medium text-foreground-secondary">{title}</h3>
|
|
15
|
+
<p className="mt-1 text-xs text-foreground-muted">{description}</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
interface ErrorBoundaryProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/** When set, renders a compact inline fallback instead of the full-page error. */
|
|
7
|
+
section?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ErrorBoundaryState {
|
|
11
|
+
hasError: boolean;
|
|
12
|
+
error: Error | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ErrorBoundary extends React.Component<
|
|
16
|
+
ErrorBoundaryProps,
|
|
17
|
+
ErrorBoundaryState
|
|
18
|
+
> {
|
|
19
|
+
constructor(props: ErrorBoundaryProps) {
|
|
20
|
+
super(props);
|
|
21
|
+
this.state = { hasError: false, error: null };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
25
|
+
return { hasError: true, error };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
29
|
+
console.error("ErrorBoundary caught an error:", error, errorInfo);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private handleRetry = () => {
|
|
33
|
+
this.setState({ hasError: false, error: null });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
if (this.state.hasError) {
|
|
38
|
+
// Section-level: compact inline fallback that doesn't take over the page
|
|
39
|
+
if (this.props.section) {
|
|
40
|
+
return (
|
|
41
|
+
<div className="rounded-lg border border-error/20 bg-error-muted p-4 text-center">
|
|
42
|
+
<p className="text-sm font-medium text-error mb-1">
|
|
43
|
+
{this.props.section} failed to load
|
|
44
|
+
</p>
|
|
45
|
+
<p className="text-xs text-foreground-muted mb-2">
|
|
46
|
+
This section encountered an error. Other sections are unaffected.
|
|
47
|
+
</p>
|
|
48
|
+
<button
|
|
49
|
+
onClick={this.handleRetry}
|
|
50
|
+
className="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-primary/90"
|
|
51
|
+
>
|
|
52
|
+
Retry
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Root-level: full-page fallback
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-6">
|
|
61
|
+
<div className="max-w-md text-center">
|
|
62
|
+
<h2 className="mb-2 text-lg font-semibold text-foreground">
|
|
63
|
+
Something went wrong
|
|
64
|
+
</h2>
|
|
65
|
+
<p className="mb-4 text-sm text-foreground-muted">
|
|
66
|
+
An unexpected error occurred. Please try again.
|
|
67
|
+
</p>
|
|
68
|
+
<button
|
|
69
|
+
onClick={() => window.location.reload()}
|
|
70
|
+
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary/90"
|
|
71
|
+
>
|
|
72
|
+
Try Again
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return this.props.children;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { formatShortId } from "@/lib/utils";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
interface FriendlyIdProps {
|
|
8
|
+
id: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function FriendlyId({ id, className }: FriendlyIdProps) {
|
|
13
|
+
const [copied, setCopied] = useState(false);
|
|
14
|
+
|
|
15
|
+
const handleCopy = async () => {
|
|
16
|
+
try {
|
|
17
|
+
await navigator.clipboard.writeText(id);
|
|
18
|
+
setCopied(true);
|
|
19
|
+
setTimeout(() => setCopied(false), 2000);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error('Failed to copy:', err);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<TooltipProvider>
|
|
27
|
+
<Tooltip>
|
|
28
|
+
<TooltipTrigger asChild>
|
|
29
|
+
<button
|
|
30
|
+
onClick={handleCopy}
|
|
31
|
+
className={cn(
|
|
32
|
+
"inline-flex items-center rounded px-1.5 py-0.5 font-mono text-xs",
|
|
33
|
+
"bg-background-secondary text-info/80",
|
|
34
|
+
"hover:bg-background-tertiary hover:text-info transition-colors cursor-pointer",
|
|
35
|
+
copied && "text-primary",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
{formatShortId(id)}
|
|
40
|
+
</button>
|
|
41
|
+
</TooltipTrigger>
|
|
42
|
+
<TooltipContent>
|
|
43
|
+
<p className="font-mono text-xs">{copied ? <span className="text-primary font-semibold">Copied!</span> : id}</p>
|
|
44
|
+
</TooltipContent>
|
|
45
|
+
</Tooltip>
|
|
46
|
+
</TooltipProvider>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cn } from "@/lib/cn";
|
|
2
|
+
|
|
3
|
+
export function Kbd({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
4
|
+
return (
|
|
5
|
+
<kbd className={cn(
|
|
6
|
+
"inline-flex h-5 min-w-[20px] items-center justify-center rounded",
|
|
7
|
+
"border border-[var(--kbd-border)] bg-background-secondary",
|
|
8
|
+
"px-1.5 font-mono text-xs leading-tight text-foreground-muted",
|
|
9
|
+
"shadow-kbd",
|
|
10
|
+
className
|
|
11
|
+
)}>
|
|
12
|
+
{children}
|
|
13
|
+
</kbd>
|
|
14
|
+
);
|
|
15
|
+
}
|