code-coordinator 0.5.46__py3-none-any.whl
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.
- code_coordinator-0.5.46.dist-info/METADATA +625 -0
- code_coordinator-0.5.46.dist-info/RECORD +295 -0
- code_coordinator-0.5.46.dist-info/WHEEL +5 -0
- code_coordinator-0.5.46.dist-info/entry_points.txt +2 -0
- code_coordinator-0.5.46.dist-info/licenses/LICENSE +110 -0
- code_coordinator-0.5.46.dist-info/top_level.txt +1 -0
- coord/__init__.py +176 -0
- coord/_board_mapping.py +229 -0
- coord/acceptance.py +468 -0
- coord/acceptance_drivers.py +632 -0
- coord/agent.py +7517 -0
- coord/agent_app.py +1555 -0
- coord/agent_update.py +417 -0
- coord/agents/opencode/.gitignore +13 -0
- coord/agents/opencode/agents/work.md +129 -0
- coord/agents/opencode/routing.jsonc +49 -0
- coord/audit.py +301 -0
- coord/auto_loop.py +1440 -0
- coord/board_bool_guard.py +72 -0
- coord/board_service.py +141 -0
- coord/board_wire.py +309 -0
- coord/brain.py +581 -0
- coord/branch_model.py +214 -0
- coord/cargo_cache.py +258 -0
- coord/ci_github.py +386 -0
- coord/ci_store.py +560 -0
- coord/claim.py +353 -0
- coord/cli.py +454 -0
- coord/client.py +610 -0
- coord/commands/__init__.py +1 -0
- coord/commands/_common.py +329 -0
- coord/commands/acceptance.py +916 -0
- coord/commands/agent_ops.py +1339 -0
- coord/commands/audit.py +131 -0
- coord/commands/chat.py +320 -0
- coord/commands/dispatch.py +1780 -0
- coord/commands/dispatch_workers.py +4894 -0
- coord/commands/drive.py +616 -0
- coord/commands/drive_queue.py +1203 -0
- coord/commands/gate_a.py +217 -0
- coord/commands/gates.py +89 -0
- coord/commands/issues.py +681 -0
- coord/commands/lifecycle.py +513 -0
- coord/commands/merge.py +1900 -0
- coord/commands/milestone.py +2081 -0
- coord/commands/plan_followup.py +1243 -0
- coord/commands/plans.py +156 -0
- coord/commands/release.py +2232 -0
- coord/commands/report.py +341 -0
- coord/commands/review.py +1523 -0
- coord/commands/scorecard.py +252 -0
- coord/commands/sessions.py +1930 -0
- coord/commands/setup.py +576 -0
- coord/commands/status.py +2089 -0
- coord/commands/terminal.py +385 -0
- coord/commands/test_gate.py +775 -0
- coord/commands/tui.py +288 -0
- coord/comments.py +718 -0
- coord/config.py +3032 -0
- coord/conflict_fix.py +633 -0
- coord/dao.py +483 -0
- coord/dashboard/__init__.py +0 -0
- coord/dashboard/fixture.py +376 -0
- coord/dashboard/index.html +658 -0
- coord/dashboard/server.py +1894 -0
- coord/dashboard/terminal.py +382 -0
- coord/dashboard/webapp/.gitignore +9 -0
- coord/dashboard/webapp/components.json +17 -0
- coord/dashboard/webapp/dist/assets/Gallery-da3qNiIw.js +71 -0
- coord/dashboard/webapp/dist/assets/Terminal-9CEnUXvW.css +32 -0
- coord/dashboard/webapp/dist/assets/Terminal-skVFCxPU.js +63 -0
- coord/dashboard/webapp/dist/assets/index-DltfZR5f.js +184 -0
- coord/dashboard/webapp/dist/assets/index-Dq4kwTdw.css +1 -0
- coord/dashboard/webapp/dist/assets/workbox-window.prod.es5-BqEJf4Xk.js +2 -0
- coord/dashboard/webapp/dist/icons/icon-192.png +0 -0
- coord/dashboard/webapp/dist/icons/icon-512.png +0 -0
- coord/dashboard/webapp/dist/icons/icon.svg +5 -0
- coord/dashboard/webapp/dist/index.html +38 -0
- coord/dashboard/webapp/dist/manifest.webmanifest +1 -0
- coord/dashboard/webapp/dist/sw.js +1 -0
- coord/dashboard/webapp/dist/workbox-e4022e15.js +1 -0
- coord/dashboard/webapp/e2e/available-gates-terminal.spec.ts +75 -0
- coord/dashboard/webapp/e2e/deep-link.spec.ts +172 -0
- coord/dashboard/webapp/e2e/fixtureServer.ts +155 -0
- coord/dashboard/webapp/e2e/live-update-fixture.spec.ts +113 -0
- coord/dashboard/webapp/e2e/realtime.spec.ts +238 -0
- coord/dashboard/webapp/e2e/shell.spec.ts +309 -0
- coord/dashboard/webapp/e2e/smoke.spec.ts +191 -0
- coord/dashboard/webapp/e2e/terminal.spec.ts +420 -0
- coord/dashboard/webapp/e2e/theme.spec.ts +138 -0
- coord/dashboard/webapp/eslint.config.js +20 -0
- coord/dashboard/webapp/index.html +37 -0
- coord/dashboard/webapp/node_modules/flatted/python/flatted.py +144 -0
- coord/dashboard/webapp/package-lock.json +10584 -0
- coord/dashboard/webapp/package.json +63 -0
- coord/dashboard/webapp/playwright.acceptance.config.ts +166 -0
- coord/dashboard/webapp/playwright.config.ts +93 -0
- coord/dashboard/webapp/postcss.config.js +6 -0
- coord/dashboard/webapp/public/icons/icon-192.png +0 -0
- coord/dashboard/webapp/public/icons/icon-512.png +0 -0
- coord/dashboard/webapp/public/icons/icon.svg +5 -0
- coord/dashboard/webapp/src/App.tsx +140 -0
- coord/dashboard/webapp/src/api/client.ts +199 -0
- coord/dashboard/webapp/src/api/generated.ts +176 -0
- coord/dashboard/webapp/src/components/ConnectionBadge.tsx +52 -0
- coord/dashboard/webapp/src/components/Detail.tsx +800 -0
- coord/dashboard/webapp/src/components/Gallery.tsx +341 -0
- coord/dashboard/webapp/src/components/Home.tsx +435 -0
- coord/dashboard/webapp/src/components/MobileKeyBar.tsx +280 -0
- coord/dashboard/webapp/src/components/PanelHeader.tsx +59 -0
- coord/dashboard/webapp/src/components/PipelineCard.tsx +168 -0
- coord/dashboard/webapp/src/components/SessionCard.tsx +99 -0
- coord/dashboard/webapp/src/components/SessionDetail.tsx +140 -0
- coord/dashboard/webapp/src/components/SessionsList.tsx +81 -0
- coord/dashboard/webapp/src/components/Terminal.tsx +376 -0
- coord/dashboard/webapp/src/components/__tests__/ConnectionBadge.test.tsx +81 -0
- coord/dashboard/webapp/src/components/__tests__/Detail.test.tsx +680 -0
- coord/dashboard/webapp/src/components/__tests__/Gallery.test.tsx +83 -0
- coord/dashboard/webapp/src/components/__tests__/Home.test.tsx +271 -0
- coord/dashboard/webapp/src/components/__tests__/MobileKeyBar.test.tsx +197 -0
- coord/dashboard/webapp/src/components/__tests__/PipelineCard.test.tsx +143 -0
- coord/dashboard/webapp/src/components/__tests__/SessionCard.test.tsx +106 -0
- coord/dashboard/webapp/src/components/__tests__/Terminal.test.tsx +504 -0
- coord/dashboard/webapp/src/components/ui/badge.tsx +41 -0
- coord/dashboard/webapp/src/components/ui/button.tsx +54 -0
- coord/dashboard/webapp/src/components/ui/card.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/dialog.tsx +99 -0
- coord/dashboard/webapp/src/components/ui/dropdown-menu.tsx +189 -0
- coord/dashboard/webapp/src/components/ui/empty-state.tsx +35 -0
- coord/dashboard/webapp/src/components/ui/sheet.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/skeleton.tsx +9 -0
- coord/dashboard/webapp/src/components/ui/tabs.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/theme-provider.tsx +78 -0
- coord/dashboard/webapp/src/components/ui/theme-toggle.tsx +20 -0
- coord/dashboard/webapp/src/components/ui/toast.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/toaster.tsx +30 -0
- coord/dashboard/webapp/src/components/ui/tooltip.tsx +26 -0
- coord/dashboard/webapp/src/components/ui/use-toast.ts +134 -0
- coord/dashboard/webapp/src/index.css +210 -0
- coord/dashboard/webapp/src/lib/pipeline.ts +29 -0
- coord/dashboard/webapp/src/lib/utils.ts +6 -0
- coord/dashboard/webapp/src/main.tsx +46 -0
- coord/dashboard/webapp/src/realtime/RealtimeProvider.tsx +112 -0
- coord/dashboard/webapp/src/realtime/__tests__/RealtimeProvider.test.tsx +189 -0
- coord/dashboard/webapp/src/realtime/__tests__/connection.test.ts +255 -0
- coord/dashboard/webapp/src/realtime/connection.ts +227 -0
- coord/dashboard/webapp/src/realtime/events.ts +100 -0
- coord/dashboard/webapp/src/routes/__tests__/paths.test.ts +92 -0
- coord/dashboard/webapp/src/routes/paths.ts +92 -0
- coord/dashboard/webapp/src/shell/ActivityRail.tsx +335 -0
- coord/dashboard/webapp/src/shell/AppShell.tsx +276 -0
- coord/dashboard/webapp/src/shell/ComingSoon.tsx +33 -0
- coord/dashboard/webapp/src/shell/EmptyDetail.tsx +26 -0
- coord/dashboard/webapp/src/shell/RouteNotFound.tsx +33 -0
- coord/dashboard/webapp/src/shell/ShellLayout.tsx +147 -0
- coord/dashboard/webapp/src/shell/StatusBar.tsx +46 -0
- coord/dashboard/webapp/src/shell/__tests__/ShellLayout.test.tsx +520 -0
- coord/dashboard/webapp/src/shell/__tests__/shellState.test.ts +95 -0
- coord/dashboard/webapp/src/shell/__tests__/stubViewport.ts +40 -0
- coord/dashboard/webapp/src/shell/breakpoints.ts +87 -0
- coord/dashboard/webapp/src/shell/railItems.ts +105 -0
- coord/dashboard/webapp/src/shell/shellState.ts +174 -0
- coord/dashboard/webapp/src/shell/useRegionFocus.ts +95 -0
- coord/dashboard/webapp/src/test-setup.ts +41 -0
- coord/dashboard/webapp/src/vite-env.d.ts +2 -0
- coord/dashboard/webapp/tailwind.config.js +140 -0
- coord/dashboard/webapp/tsconfig.json +25 -0
- coord/dashboard/webapp/tsconfig.node.json +11 -0
- coord/dashboard/webapp/vite.config.ts +71 -0
- coord/db.py +1076 -0
- coord/dead_end.py +332 -0
- coord/deploy/README.md +33 -0
- coord/deploy/coord-agent.service +89 -0
- coord/deploy/coord-db-backup.service +60 -0
- coord/deploy/coord-db-backup.sh +74 -0
- coord/deploy/coord-db-backup.timer +18 -0
- coord/deploy/coord-drive-queue.service +117 -0
- coord/deploy/coord-drive-queue.timer +39 -0
- coord/deploy/coord-notify.service +48 -0
- coord/deploy/coord-notify.timer +24 -0
- coord/deploy/coord-release-propagate.service +83 -0
- coord/deploy/coord-release-propagate.timer +38 -0
- coord/deploy/coord-release-window.service +119 -0
- coord/deploy/coord-release-window.timer +36 -0
- coord/deploy/coord-serve.service +82 -0
- coord/deploy/coord-web-dist-build.service +43 -0
- coord/deploy/coord-web-dist-build.timer +36 -0
- coord/deploy/coord-web.service +125 -0
- coord/deploy_manifest.py +80 -0
- coord/deploy_units.py +384 -0
- coord/deps.py +115 -0
- coord/diagnose.py +1623 -0
- coord/dispatch.py +1009 -0
- coord/dist_name.py +123 -0
- coord/drive.py +3101 -0
- coord/drive_queue.py +2298 -0
- coord/drive_state.py +870 -0
- coord/events.py +381 -0
- coord/failure_class.py +914 -0
- coord/filelock.py +168 -0
- coord/fleet_config_health.py +300 -0
- coord/freshness.py +206 -0
- coord/gate_a.py +469 -0
- coord/gate_b.py +411 -0
- coord/gate_snapshot.py +385 -0
- coord/gates.py +582 -0
- coord/github_ops.py +1954 -0
- coord/goal.py +125 -0
- coord/graph_health.py +348 -0
- coord/health/__init__.py +69 -0
- coord/health/aggregate.py +129 -0
- coord/health/checks/__init__.py +13 -0
- coord/health/checks/agent_install.py +280 -0
- coord/health/checks/cargo_targets.py +171 -0
- coord/health/checks/claude_binary.py +65 -0
- coord/health/checks/deploy_lane_facts.py +458 -0
- coord/health/checks/disk.py +99 -0
- coord/health/checks/fleet_board.py +89 -0
- coord/health/checks/fleet_deploy_lanes.py +469 -0
- coord/health/checks/fleet_phantom.py +69 -0
- coord/health/checks/fleet_unit_drift.py +151 -0
- coord/health/checks/graph.py +192 -0
- coord/health/checks/plan_usage.py +88 -0
- coord/health/checks/repo_state.py +161 -0
- coord/health/checks/spawned_coord.py +465 -0
- coord/health/checks/timer_active.py +254 -0
- coord/health/checks/toolchain.py +547 -0
- coord/health/checks/unit_drift.py +648 -0
- coord/health/checks/unit_enablement.py +171 -0
- coord/health/checks/worktrees.py +96 -0
- coord/health/cli.py +121 -0
- coord/health/context.py +106 -0
- coord/health/fleet_snapshot.py +477 -0
- coord/health/models.py +250 -0
- coord/health/pypi.py +231 -0
- coord/health/registry.py +240 -0
- coord/health/render.py +82 -0
- coord/health/units.py +60 -0
- coord/hooks.py +106 -0
- coord/housekeeping.py +204 -0
- coord/interactive.py +4286 -0
- coord/issue_store.py +1496 -0
- coord/liveness_auditor.py +293 -0
- coord/machine_pause.py +755 -0
- coord/merge_queue.py +4681 -0
- coord/milestone_chat.py +600 -0
- coord/milestone_dispatch.py +943 -0
- coord/milestone_gate.py +709 -0
- coord/milestone_order.py +840 -0
- coord/mock_author.py +334 -0
- coord/models.py +891 -0
- coord/network.py +269 -0
- coord/new_issue_chat.py +229 -0
- coord/notify.py +3226 -0
- coord/openapi.py +404 -0
- coord/overlap_fence.py +133 -0
- coord/parentage.py +200 -0
- coord/parentage_github.py +58 -0
- coord/pipeline.py +481 -0
- coord/plan_parser.py +266 -0
- coord/plans.py +543 -0
- coord/platform_paths.py +43 -0
- coord/pr_body_lint.py +67 -0
- coord/prereqs.py +533 -0
- coord/progress.py +425 -0
- coord/providers/__init__.py +683 -0
- coord/providers/base.py +218 -0
- coord/providers/claude.py +284 -0
- coord/providers/claude_pty.py +610 -0
- coord/providers/opencode.py +896 -0
- coord/reconcile.py +2233 -0
- coord/refine_chat.py +485 -0
- coord/release_cordon.py +525 -0
- coord/release_propagate.py +1176 -0
- coord/release_verify.py +777 -0
- coord/release_window.py +322 -0
- coord/reports.py +1643 -0
- coord/revalidate.py +1101 -0
- coord/review.py +3317 -0
- coord/scorecard.py +484 -0
- coord/serve_app.py +7192 -0
- coord/skills/update-issue/SKILL.md +93 -0
- coord/smoke.py +1030 -0
- coord/split_work.py +210 -0
- coord/stage_projection.py +650 -0
- coord/state.py +5720 -0
- coord/test_author.py +1064 -0
- coord/test_chat.py +352 -0
- coord/test_orchestrator.py +494 -0
- coord/test_report.py +178 -0
- coord/tui_release.py +271 -0
- coord/usage.py +753 -0
- coord/usage_limits.py +358 -0
- coord/usage_rollup.py +709 -0
- coord/worker_events.py +954 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as ToastPrimitives from '@radix-ui/react-toast'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { X } from 'lucide-react'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
|
|
7
|
+
const ToastProvider = ToastPrimitives.Provider
|
|
8
|
+
|
|
9
|
+
const ToastViewport = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<ToastPrimitives.Viewport
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
'fixed bottom-0 right-0 z-[100] flex max-h-screen w-full flex-col-reverse gap-2 p-4 sm:bottom-4 sm:right-4 sm:top-auto sm:max-w-[380px]',
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
))
|
|
22
|
+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
|
23
|
+
|
|
24
|
+
const toastVariants = cva(
|
|
25
|
+
'group pointer-events-auto relative flex w-full items-start justify-between gap-3 overflow-hidden rounded-md border p-4 shadow-elevation transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
variant: {
|
|
29
|
+
default: 'border-line-strong bg-popover text-popover-foreground',
|
|
30
|
+
success: 'border-pass bg-pass-wash text-pass',
|
|
31
|
+
warning: 'border-attn bg-attn-wash text-attn',
|
|
32
|
+
destructive: 'border-fail bg-fail-wash text-fail',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: 'default',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const Toast = React.forwardRef<
|
|
42
|
+
React.ElementRef<typeof ToastPrimitives.Root>,
|
|
43
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
|
|
44
|
+
>(({ className, variant, ...props }, ref) => (
|
|
45
|
+
<ToastPrimitives.Root
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={cn(toastVariants({ variant }), className)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
))
|
|
51
|
+
Toast.displayName = ToastPrimitives.Root.displayName
|
|
52
|
+
|
|
53
|
+
const ToastAction = React.forwardRef<
|
|
54
|
+
React.ElementRef<typeof ToastPrimitives.Action>,
|
|
55
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
|
56
|
+
>(({ className, ...props }, ref) => (
|
|
57
|
+
<ToastPrimitives.Action
|
|
58
|
+
ref={ref}
|
|
59
|
+
className={cn(
|
|
60
|
+
'inline-flex h-8 shrink-0 items-center justify-center rounded-sm border border-line-strong bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
))
|
|
66
|
+
ToastAction.displayName = ToastPrimitives.Action.displayName
|
|
67
|
+
|
|
68
|
+
const ToastClose = React.forwardRef<
|
|
69
|
+
React.ElementRef<typeof ToastPrimitives.Close>,
|
|
70
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
|
71
|
+
>(({ className, ...props }, ref) => (
|
|
72
|
+
<ToastPrimitives.Close
|
|
73
|
+
ref={ref}
|
|
74
|
+
className={cn(
|
|
75
|
+
'absolute right-2 top-2 rounded-sm p-1 text-current opacity-0 transition-opacity hover:opacity-100 focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-70',
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
toast-close=""
|
|
79
|
+
{...props}
|
|
80
|
+
>
|
|
81
|
+
<X className="h-4 w-4" />
|
|
82
|
+
</ToastPrimitives.Close>
|
|
83
|
+
))
|
|
84
|
+
ToastClose.displayName = ToastPrimitives.Close.displayName
|
|
85
|
+
|
|
86
|
+
const ToastTitle = React.forwardRef<
|
|
87
|
+
React.ElementRef<typeof ToastPrimitives.Title>,
|
|
88
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
|
89
|
+
>(({ className, ...props }, ref) => (
|
|
90
|
+
<ToastPrimitives.Title
|
|
91
|
+
ref={ref}
|
|
92
|
+
className={cn('text-sm font-semibold', className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
))
|
|
96
|
+
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
|
97
|
+
|
|
98
|
+
const ToastDescription = React.forwardRef<
|
|
99
|
+
React.ElementRef<typeof ToastPrimitives.Description>,
|
|
100
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
|
101
|
+
>(({ className, ...props }, ref) => (
|
|
102
|
+
<ToastPrimitives.Description
|
|
103
|
+
ref={ref}
|
|
104
|
+
className={cn('text-sm opacity-90', className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
))
|
|
108
|
+
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
|
109
|
+
|
|
110
|
+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
|
111
|
+
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
type ToastProps,
|
|
115
|
+
type ToastActionElement,
|
|
116
|
+
ToastProvider,
|
|
117
|
+
ToastViewport,
|
|
118
|
+
Toast,
|
|
119
|
+
ToastTitle,
|
|
120
|
+
ToastDescription,
|
|
121
|
+
ToastClose,
|
|
122
|
+
ToastAction,
|
|
123
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useToast } from '@/components/ui/use-toast'
|
|
2
|
+
import {
|
|
3
|
+
Toast,
|
|
4
|
+
ToastClose,
|
|
5
|
+
ToastDescription,
|
|
6
|
+
ToastProvider,
|
|
7
|
+
ToastTitle,
|
|
8
|
+
ToastViewport,
|
|
9
|
+
} from '@/components/ui/toast'
|
|
10
|
+
|
|
11
|
+
/** Mounted once at the app root (see App.tsx); renders whatever `toast()` (use-toast.ts) queues. */
|
|
12
|
+
export function Toaster() {
|
|
13
|
+
const { toasts } = useToast()
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ToastProvider>
|
|
17
|
+
{toasts.map(({ id, title, description, action, ...props }) => (
|
|
18
|
+
<Toast key={id} {...props}>
|
|
19
|
+
<div className="grid gap-1">
|
|
20
|
+
{title && <ToastTitle>{title}</ToastTitle>}
|
|
21
|
+
{description && <ToastDescription>{description}</ToastDescription>}
|
|
22
|
+
</div>
|
|
23
|
+
{action}
|
|
24
|
+
<ToastClose />
|
|
25
|
+
</Toast>
|
|
26
|
+
))}
|
|
27
|
+
<ToastViewport />
|
|
28
|
+
</ToastProvider>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
6
|
+
const Tooltip = TooltipPrimitive.Root
|
|
7
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
8
|
+
|
|
9
|
+
const TooltipContent = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
12
|
+
>(({ className, sideOffset = 6, ...props }, ref) => (
|
|
13
|
+
<TooltipPrimitive.Content
|
|
14
|
+
ref={ref}
|
|
15
|
+
sideOffset={sideOffset}
|
|
16
|
+
className={cn(
|
|
17
|
+
'z-50 overflow-hidden rounded-sm border border-line-strong bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-elevation',
|
|
18
|
+
'data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:zoom-in-95',
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
))
|
|
24
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
25
|
+
|
|
26
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Adapted from shadcn/ui's toast hook (itself adapted from react-hot-toast) —
|
|
2
|
+
// a module-level store outside React so any component can call `toast(...)`
|
|
3
|
+
// without threading a context down, while `useToast()` still re-renders
|
|
4
|
+
// subscribers (namely <Toaster/>) on change.
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
|
|
7
|
+
|
|
8
|
+
const TOAST_LIMIT = 3
|
|
9
|
+
const TOAST_REMOVE_DELAY = 4000
|
|
10
|
+
|
|
11
|
+
type ToasterToast = ToastProps & {
|
|
12
|
+
id: string
|
|
13
|
+
title?: React.ReactNode
|
|
14
|
+
description?: React.ReactNode
|
|
15
|
+
action?: ToastActionElement
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let count = 0
|
|
19
|
+
|
|
20
|
+
function genId() {
|
|
21
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
|
22
|
+
return count.toString()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Action =
|
|
26
|
+
| { type: 'ADD_TOAST'; toast: ToasterToast }
|
|
27
|
+
| { type: 'UPDATE_TOAST'; toast: Partial<ToasterToast> }
|
|
28
|
+
| { type: 'DISMISS_TOAST'; toastId?: ToasterToast['id'] }
|
|
29
|
+
| { type: 'REMOVE_TOAST'; toastId?: ToasterToast['id'] }
|
|
30
|
+
|
|
31
|
+
interface State {
|
|
32
|
+
toasts: ToasterToast[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
36
|
+
|
|
37
|
+
function addToRemoveQueue(toastId: string) {
|
|
38
|
+
if (toastTimeouts.has(toastId)) return
|
|
39
|
+
|
|
40
|
+
const timeout = setTimeout(() => {
|
|
41
|
+
toastTimeouts.delete(toastId)
|
|
42
|
+
dispatch({ type: 'REMOVE_TOAST', toastId })
|
|
43
|
+
}, TOAST_REMOVE_DELAY)
|
|
44
|
+
|
|
45
|
+
toastTimeouts.set(toastId, timeout)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function reducer(state: State, action: Action): State {
|
|
49
|
+
switch (action.type) {
|
|
50
|
+
case 'ADD_TOAST':
|
|
51
|
+
return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) }
|
|
52
|
+
|
|
53
|
+
case 'UPDATE_TOAST':
|
|
54
|
+
return {
|
|
55
|
+
...state,
|
|
56
|
+
toasts: state.toasts.map(t => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'DISMISS_TOAST': {
|
|
60
|
+
const { toastId } = action
|
|
61
|
+
if (toastId) {
|
|
62
|
+
addToRemoveQueue(toastId)
|
|
63
|
+
} else {
|
|
64
|
+
state.toasts.forEach(t => addToRemoveQueue(t.id))
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
toasts: state.toasts.map(t =>
|
|
69
|
+
t.id === toastId || toastId === undefined ? { ...t, open: false } : t,
|
|
70
|
+
),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case 'REMOVE_TOAST':
|
|
75
|
+
if (action.toastId === undefined) {
|
|
76
|
+
return { ...state, toasts: [] }
|
|
77
|
+
}
|
|
78
|
+
return { ...state, toasts: state.toasts.filter(t => t.id !== action.toastId) }
|
|
79
|
+
|
|
80
|
+
default:
|
|
81
|
+
return state
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const listeners: Array<(state: State) => void> = []
|
|
86
|
+
let memoryState: State = { toasts: [] }
|
|
87
|
+
|
|
88
|
+
function dispatch(action: Action) {
|
|
89
|
+
memoryState = reducer(memoryState, action)
|
|
90
|
+
listeners.forEach(listener => listener(memoryState))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type Toast = Omit<ToasterToast, 'id'>
|
|
94
|
+
|
|
95
|
+
function toast({ ...props }: Toast) {
|
|
96
|
+
const id = genId()
|
|
97
|
+
|
|
98
|
+
const update = (props: ToasterToast) => dispatch({ type: 'UPDATE_TOAST', toast: { ...props, id } })
|
|
99
|
+
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
|
|
100
|
+
|
|
101
|
+
dispatch({
|
|
102
|
+
type: 'ADD_TOAST',
|
|
103
|
+
toast: {
|
|
104
|
+
...props,
|
|
105
|
+
id,
|
|
106
|
+
open: true,
|
|
107
|
+
onOpenChange: (open: boolean) => {
|
|
108
|
+
if (!open) dismiss()
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return { id, dismiss, update }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function useToast() {
|
|
117
|
+
const [state, setState] = React.useState<State>(memoryState)
|
|
118
|
+
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
listeners.push(setState)
|
|
121
|
+
return () => {
|
|
122
|
+
const index = listeners.indexOf(setState)
|
|
123
|
+
if (index > -1) listeners.splice(index, 1)
|
|
124
|
+
}
|
|
125
|
+
}, [])
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...state,
|
|
129
|
+
toast,
|
|
130
|
+
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { useToast, toast }
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* coord web — design tokens (#1546)
|
|
7
|
+
*
|
|
8
|
+
* The raw palette below is copied verbatim from the Gate-A design mocks'
|
|
9
|
+
* shared reference, docs/mocks/web/_tokens.css — that file says explicitly
|
|
10
|
+
* it is "the seed for W1-1's real token layer." Keep the two in sync; if a
|
|
11
|
+
* value changes here, change it there too (or vice versa).
|
|
12
|
+
*
|
|
13
|
+
* Dark is the default and the preferred reading mode; light is a real,
|
|
14
|
+
* designed second theme, not a mechanical inversion. Both are switched by
|
|
15
|
+
* `data-theme` on <html> (see src/components/theme-provider.tsx) rather
|
|
16
|
+
* than Tailwind's `dark:` class variant, because that's what the mocks
|
|
17
|
+
* already do and what avoids a flash of the wrong theme (the attribute is
|
|
18
|
+
* set by a blocking inline script in index.html before paint).
|
|
19
|
+
*
|
|
20
|
+
* A second layer of semantic aliases (--background, --foreground, --card,
|
|
21
|
+
* --primary, ...) maps the raw palette onto the shadcn/ui vocabulary that
|
|
22
|
+
* ui/* primitives and pre-existing components (PipelineCard,
|
|
23
|
+
* ConnectionBadge, MobileKeyBar) consume — see tailwind.config.js for the
|
|
24
|
+
* full rationale, including why shadcn's `accent` slot is kept pointed at
|
|
25
|
+
* a neutral hover surface instead of the mock's cyan "brand" accent.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
@layer base {
|
|
29
|
+
:root {
|
|
30
|
+
/* ── raw palette — type ──────────────────────────────────────────── */
|
|
31
|
+
--font-ui: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
|
|
32
|
+
'Helvetica Neue', Arial, sans-serif;
|
|
33
|
+
/* mono is reserved for values the machine owns: issue numbers,
|
|
34
|
+
branches, SHAs, machine names, durations, costs. Never for chrome. */
|
|
35
|
+
--font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
|
|
36
|
+
'Liberation Mono', monospace;
|
|
37
|
+
|
|
38
|
+
--step--1: 0.75rem;
|
|
39
|
+
--step-0: 0.875rem;
|
|
40
|
+
--step-1: 1rem;
|
|
41
|
+
--step-2: 1.25rem;
|
|
42
|
+
--step-3: 1.5rem;
|
|
43
|
+
|
|
44
|
+
/* ── raw palette — geometry ──────────────────────────────────────── */
|
|
45
|
+
--r-sm: 5px;
|
|
46
|
+
--r-md: 8px;
|
|
47
|
+
--r-lg: 12px;
|
|
48
|
+
|
|
49
|
+
--rail-w: 216px;
|
|
50
|
+
--rail-w-collapsed: 60px;
|
|
51
|
+
--panel-w: 360px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* `:root` carries the dark values too (dark is the default, and it's the
|
|
55
|
+
fallback if `data-theme` is ever missing) -- but the SAME selector list
|
|
56
|
+
also includes the bare `[data-theme='dark']` attribute, not just
|
|
57
|
+
`:root[data-theme='dark']`. That's deliberate: it's what lets a nested
|
|
58
|
+
subtree (e.g. the gallery's side-by-side theme preview,
|
|
59
|
+
src/components/Gallery.tsx) force dark back on, even inside a page
|
|
60
|
+
whose <html> is already light -- a plain `:root` rule only ever matches
|
|
61
|
+
the root element, so without this a nested `data-theme="dark"` div
|
|
62
|
+
would have no rule to reset it and would just inherit light. */
|
|
63
|
+
:root,
|
|
64
|
+
[data-theme='dark'] {
|
|
65
|
+
/* ── raw palette — ground & surfaces ──────────────────────────── */
|
|
66
|
+
--ground: #101418;
|
|
67
|
+
--surface: #171c22;
|
|
68
|
+
--surface-2: #1e242b;
|
|
69
|
+
--surface-3: #252d36;
|
|
70
|
+
--line: #2a323b;
|
|
71
|
+
--line-strong: #3a444f;
|
|
72
|
+
|
|
73
|
+
/* ── raw palette — text ──────────────────────────────────────────── */
|
|
74
|
+
--text: #dfe6ee;
|
|
75
|
+
--text-dim: #8b97a5;
|
|
76
|
+
--text-faint: #5f6b78;
|
|
77
|
+
|
|
78
|
+
/* ── raw palette — accent: "work is happening here" ────────────────
|
|
79
|
+
Below AA (~3.4:1) against --ground; see the README note under
|
|
80
|
+
`.text-faint` usage in the gallery for why that's an intentional,
|
|
81
|
+
carried-over Gate-A choice rather than an oversight. */
|
|
82
|
+
--accent: #4fb8cf;
|
|
83
|
+
--accent-dim: #2d7688;
|
|
84
|
+
--accent-wash: rgba(79, 184, 207, 0.12);
|
|
85
|
+
|
|
86
|
+
/* ── raw palette — semantic state (deliberately desaturated) ──────── */
|
|
87
|
+
--pass: #4ea87a;
|
|
88
|
+
--pass-wash: rgba(78, 168, 122, 0.14);
|
|
89
|
+
--attn: #d9a54c;
|
|
90
|
+
--attn-wash: rgba(217, 165, 76, 0.14);
|
|
91
|
+
--fail: #d9605e;
|
|
92
|
+
--fail-wash: rgba(217, 96, 94, 0.14);
|
|
93
|
+
--idle: #55606d;
|
|
94
|
+
--idle-wash: rgba(85, 96, 109, 0.14);
|
|
95
|
+
|
|
96
|
+
/* ── semantic aliases (shadcn/ui vocabulary) ────────────────────── */
|
|
97
|
+
--background: var(--ground);
|
|
98
|
+
--foreground: var(--text);
|
|
99
|
+
|
|
100
|
+
--card: var(--surface);
|
|
101
|
+
--card-foreground: var(--text);
|
|
102
|
+
--popover: var(--surface-2);
|
|
103
|
+
--popover-foreground: var(--text);
|
|
104
|
+
|
|
105
|
+
--primary: var(--accent);
|
|
106
|
+
--primary-foreground: #08161a;
|
|
107
|
+
--secondary: var(--surface-2);
|
|
108
|
+
--secondary-foreground: var(--text);
|
|
109
|
+
--muted: var(--surface-2);
|
|
110
|
+
--muted-foreground: var(--text-dim);
|
|
111
|
+
|
|
112
|
+
/* Neutral hover/press highlight — NOT the brand cyan. Existing
|
|
113
|
+
components (e.g. MobileKeyBar's `active:bg-accent`) already depend
|
|
114
|
+
on this meaning; see tailwind.config.js. */
|
|
115
|
+
--accent-surface: var(--surface-2);
|
|
116
|
+
--accent-surface-foreground: var(--text);
|
|
117
|
+
|
|
118
|
+
--destructive: var(--fail);
|
|
119
|
+
--destructive-foreground: #ffffff;
|
|
120
|
+
|
|
121
|
+
--border: var(--line);
|
|
122
|
+
--input: var(--line);
|
|
123
|
+
--ring: var(--accent);
|
|
124
|
+
--radius: var(--r-md);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Not scoped to :root: same reasoning as `[data-theme='dark']` above. */
|
|
128
|
+
[data-theme='light'] {
|
|
129
|
+
--ground: #f5f7f9;
|
|
130
|
+
--surface: #ffffff;
|
|
131
|
+
--surface-2: #eef1f5;
|
|
132
|
+
--surface-3: #e3e8ee;
|
|
133
|
+
--line: #dde3ea;
|
|
134
|
+
--line-strong: #c3ccd6;
|
|
135
|
+
|
|
136
|
+
--text: #1a212a;
|
|
137
|
+
--text-dim: #5b6672;
|
|
138
|
+
--text-faint: #8a95a1;
|
|
139
|
+
|
|
140
|
+
--accent: #1d7f96;
|
|
141
|
+
--accent-dim: #8fc9d6;
|
|
142
|
+
--accent-wash: rgba(29, 127, 150, 0.1);
|
|
143
|
+
|
|
144
|
+
--pass: #2f7d55;
|
|
145
|
+
--pass-wash: rgba(47, 125, 85, 0.11);
|
|
146
|
+
--attn: #a4741d;
|
|
147
|
+
--attn-wash: rgba(164, 116, 29, 0.12);
|
|
148
|
+
--fail: #b8403e;
|
|
149
|
+
--fail-wash: rgba(184, 64, 62, 0.11);
|
|
150
|
+
--idle: #97a1ac;
|
|
151
|
+
--idle-wash: rgba(151, 161, 172, 0.14);
|
|
152
|
+
|
|
153
|
+
/* ── semantic aliases — redeclared, not inherited ─────────────────
|
|
154
|
+
Custom properties resolve `var(--x)` against the *declaring*
|
|
155
|
+
element, then inherit the already-resolved value -- a descendant
|
|
156
|
+
that overrides --ground doesn't retroactively change what an
|
|
157
|
+
ancestor's `--background: var(--ground)` computed to. So every
|
|
158
|
+
alias needs restating here against the raw tokens just set above,
|
|
159
|
+
or `bg-background`/`text-foreground`/etc. would silently keep
|
|
160
|
+
whatever the dark block resolved them to. (Learned by screenshotting
|
|
161
|
+
the gallery's side-by-side preview mid-build: without this, the
|
|
162
|
+
"light" pane rendered with dark colours.) */
|
|
163
|
+
--background: var(--ground);
|
|
164
|
+
--foreground: var(--text);
|
|
165
|
+
|
|
166
|
+
--card: var(--surface);
|
|
167
|
+
--card-foreground: var(--text);
|
|
168
|
+
--popover: var(--surface-2);
|
|
169
|
+
--popover-foreground: var(--text);
|
|
170
|
+
|
|
171
|
+
--primary: var(--accent);
|
|
172
|
+
/* Light theme's --accent is a darker teal than dark theme's bright
|
|
173
|
+
cyan, so the button text that sits on top of it needs to flip from
|
|
174
|
+
dark ink to white to keep 4.5:1 contrast (measured ~4.6:1 white vs
|
|
175
|
+
~4.0:1 black). */
|
|
176
|
+
--primary-foreground: #ffffff;
|
|
177
|
+
--secondary: var(--surface-2);
|
|
178
|
+
--secondary-foreground: var(--text);
|
|
179
|
+
--muted: var(--surface-2);
|
|
180
|
+
--muted-foreground: var(--text-dim);
|
|
181
|
+
|
|
182
|
+
--accent-surface: var(--surface-2);
|
|
183
|
+
--accent-surface-foreground: var(--text);
|
|
184
|
+
|
|
185
|
+
--destructive: var(--fail);
|
|
186
|
+
--destructive-foreground: #ffffff;
|
|
187
|
+
|
|
188
|
+
--border: var(--line);
|
|
189
|
+
--input: var(--line);
|
|
190
|
+
--ring: var(--accent);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@layer base {
|
|
195
|
+
* {
|
|
196
|
+
@apply border-border;
|
|
197
|
+
}
|
|
198
|
+
body {
|
|
199
|
+
@apply bg-background text-foreground;
|
|
200
|
+
font-family: var(--font-ui);
|
|
201
|
+
-webkit-font-smoothing: antialiased;
|
|
202
|
+
-moz-osx-font-smoothing: grayscale;
|
|
203
|
+
}
|
|
204
|
+
/* Matches the mocks' `.mono` convenience class: tabular figures so
|
|
205
|
+
durations/costs/ids don't jitter as digits change. */
|
|
206
|
+
.mono {
|
|
207
|
+
font-family: var(--font-mono);
|
|
208
|
+
font-variant-numeric: tabular-nums;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline predicates shared between the Pipeline panel and the shell.
|
|
3
|
+
*
|
|
4
|
+
* Lifted out of `Home.tsx` (#1547) because the activity rail needs the same
|
|
5
|
+
* two answers Home's filter tabs need — how many items are in flight, and how
|
|
6
|
+
* many are waiting on a human — and a second, independently-drifting copy of
|
|
7
|
+
* "what counts as active" in the rail would be a lie the moment either
|
|
8
|
+
* definition changed.
|
|
9
|
+
*/
|
|
10
|
+
import type { PipelineView } from '@/api/client'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* "Active": items that haven't finished (current_stage !== "merged").
|
|
14
|
+
* Keeps the list (and the rail count) focused on in-flight work without
|
|
15
|
+
* cluttering with history.
|
|
16
|
+
*/
|
|
17
|
+
export function isActive(view: PipelineView): boolean {
|
|
18
|
+
return view.current_stage !== 'merged'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* "Needs me": items where at least one human gate action is available.
|
|
23
|
+
* E.g.: work done (needs test dispatch), review approved (needs merge queue),
|
|
24
|
+
* smoke passed (needs merge queue), merge ready (needs merge), failures (need
|
|
25
|
+
* retry/fix).
|
|
26
|
+
*/
|
|
27
|
+
export function needsMe(view: PipelineView): boolean {
|
|
28
|
+
return view.available_gates.length > 0
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
4
|
+
import App from './App'
|
|
5
|
+
import { RealtimeProvider } from './realtime/RealtimeProvider'
|
|
6
|
+
import './index.css'
|
|
7
|
+
|
|
8
|
+
const queryClient = new QueryClient({
|
|
9
|
+
defaultOptions: {
|
|
10
|
+
queries: {
|
|
11
|
+
// #1549: no timer-based polling. The SSE `/events` stream
|
|
12
|
+
// (RealtimeProvider) invalidates exactly the query keys each server
|
|
13
|
+
// event affects, and a stream recovery (reconnecting -> live) triggers
|
|
14
|
+
// an explicit resync -- see src/realtime/events.ts. Data is therefore
|
|
15
|
+
// fresh until something says otherwise, which `refetchOnWindowFocus`'s
|
|
16
|
+
// default (true) still backstops for a tab that was backgrounded long
|
|
17
|
+
// enough to miss its own reconnect.
|
|
18
|
+
//
|
|
19
|
+
// Footgun for the next query added to this app: `staleTime: Infinity`
|
|
20
|
+
// is a *global* default. A `useQuery` whose key isn't wired into
|
|
21
|
+
// `EVENT_QUERY_KEYS` (src/realtime/events.ts) or manually invalidated
|
|
22
|
+
// elsewhere will never refetch on its own -- only `refetchOnWindowFocus`
|
|
23
|
+
// (or an explicit `invalidateQueries` call, e.g. Detail.tsx's actions)
|
|
24
|
+
// will ever refresh it. If you add a query, add its invalidation path
|
|
25
|
+
// in the same PR.
|
|
26
|
+
staleTime: Infinity,
|
|
27
|
+
// Retry once on error before surfacing the failure state
|
|
28
|
+
retry: 1,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Register service worker (vite-plugin-pwa injects this at build time)
|
|
34
|
+
// In dev mode the import is a no-op stub from the virtual module.
|
|
35
|
+
import { registerSW } from 'virtual:pwa-register'
|
|
36
|
+
registerSW({ immediate: true })
|
|
37
|
+
|
|
38
|
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|
39
|
+
<React.StrictMode>
|
|
40
|
+
<QueryClientProvider client={queryClient}>
|
|
41
|
+
<RealtimeProvider>
|
|
42
|
+
<App />
|
|
43
|
+
</RealtimeProvider>
|
|
44
|
+
</QueryClientProvider>
|
|
45
|
+
</React.StrictMode>,
|
|
46
|
+
)
|