@legioncodeinc/hive 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +2 -2
  2. package/dist/daemon/dashboard/app.js +26 -25
  3. package/dist/daemon/dashboard/host.d.ts +7 -0
  4. package/dist/daemon/dashboard/host.js +16 -0
  5. package/dist/daemon/dashboard/host.js.map +1 -1
  6. package/dist/daemon/dashboard/web-assets.d.ts +2 -0
  7. package/dist/daemon/dashboard/web-assets.js +19 -0
  8. package/dist/daemon/dashboard/web-assets.js.map +1 -1
  9. package/dist/daemon/gate.d.ts +2 -2
  10. package/dist/daemon/gate.js +15 -4
  11. package/dist/daemon/gate.js.map +1 -1
  12. package/dist/daemon/installer/bin-resolver.d.ts +34 -0
  13. package/dist/daemon/installer/bin-resolver.js +86 -0
  14. package/dist/daemon/installer/bin-resolver.js.map +1 -0
  15. package/dist/daemon/installer/config.d.ts +63 -0
  16. package/dist/daemon/installer/config.js +74 -0
  17. package/dist/daemon/installer/config.js.map +1 -0
  18. package/dist/daemon/installer/detection.d.ts +20 -0
  19. package/dist/daemon/installer/detection.js +73 -0
  20. package/dist/daemon/installer/detection.js.map +1 -0
  21. package/dist/daemon/installer/funnel-telemetry.d.ts +54 -0
  22. package/dist/daemon/installer/funnel-telemetry.js +134 -0
  23. package/dist/daemon/installer/funnel-telemetry.js.map +1 -0
  24. package/dist/daemon/installer/index.d.ts +12 -0
  25. package/dist/daemon/installer/index.js +10 -0
  26. package/dist/daemon/installer/index.js.map +1 -0
  27. package/dist/daemon/installer/install-state.d.ts +50 -0
  28. package/dist/daemon/installer/install-state.js +142 -0
  29. package/dist/daemon/installer/install-state.js.map +1 -0
  30. package/dist/daemon/installer/manifest-snapshot.json +25 -0
  31. package/dist/daemon/installer/manifest.d.ts +47 -0
  32. package/dist/daemon/installer/manifest.js +103 -0
  33. package/dist/daemon/installer/manifest.js.map +1 -0
  34. package/dist/daemon/installer/products.d.ts +33 -0
  35. package/dist/daemon/installer/products.js +42 -0
  36. package/dist/daemon/installer/products.js.map +1 -0
  37. package/dist/daemon/installer/routes.d.ts +43 -0
  38. package/dist/daemon/installer/routes.js +201 -0
  39. package/dist/daemon/installer/routes.js.map +1 -0
  40. package/dist/daemon/installer/security.d.ts +33 -0
  41. package/dist/daemon/installer/security.js +80 -0
  42. package/dist/daemon/installer/security.js.map +1 -0
  43. package/dist/daemon/installer/spawn.d.ts +48 -0
  44. package/dist/daemon/installer/spawn.js +51 -0
  45. package/dist/daemon/installer/spawn.js.map +1 -0
  46. package/dist/daemon/installer/token.d.ts +23 -0
  47. package/dist/daemon/installer/token.js +56 -0
  48. package/dist/daemon/installer/token.js.map +1 -0
  49. package/dist/daemon/server.d.ts +6 -0
  50. package/dist/daemon/server.js +7 -0
  51. package/dist/daemon/server.js.map +1 -1
  52. package/dist/dashboard/web/app.js +42 -20
  53. package/dist/dashboard/web/app.js.map +1 -1
  54. package/dist/dashboard/web/boot-route.d.ts +11 -7
  55. package/dist/dashboard/web/boot-route.js +12 -6
  56. package/dist/dashboard/web/boot-route.js.map +1 -1
  57. package/dist/dashboard/web/main.js +2 -1
  58. package/dist/dashboard/web/main.js.map +1 -1
  59. package/dist/dashboard/web/onboarding/advanced-picker.d.ts +16 -0
  60. package/dist/dashboard/web/onboarding/advanced-picker.js +59 -0
  61. package/dist/dashboard/web/onboarding/advanced-picker.js.map +1 -0
  62. package/dist/dashboard/web/onboarding/contracts.d.ts +188 -0
  63. package/dist/dashboard/web/onboarding/contracts.js +161 -0
  64. package/dist/dashboard/web/onboarding/contracts.js.map +1 -0
  65. package/dist/dashboard/web/onboarding/health-view.d.ts +17 -0
  66. package/dist/dashboard/web/onboarding/health-view.js +79 -0
  67. package/dist/dashboard/web/onboarding/health-view.js.map +1 -0
  68. package/dist/dashboard/web/onboarding/install-card.d.ts +27 -0
  69. package/dist/dashboard/web/onboarding/install-card.js +170 -0
  70. package/dist/dashboard/web/onboarding/install-card.js.map +1 -0
  71. package/dist/dashboard/web/onboarding/login-step.d.ts +29 -0
  72. package/dist/dashboard/web/onboarding/login-step.js +104 -0
  73. package/dist/dashboard/web/onboarding/login-step.js.map +1 -0
  74. package/dist/dashboard/web/onboarding/onboarding-client.d.ts +52 -0
  75. package/dist/dashboard/web/onboarding/onboarding-client.js +133 -0
  76. package/dist/dashboard/web/onboarding/onboarding-client.js.map +1 -0
  77. package/dist/dashboard/web/onboarding/onboarding-hero.d.ts +24 -0
  78. package/dist/dashboard/web/onboarding/onboarding-hero.js +70 -0
  79. package/dist/dashboard/web/onboarding/onboarding-hero.js.map +1 -0
  80. package/dist/dashboard/web/onboarding/onboarding-screen.d.ts +43 -0
  81. package/dist/dashboard/web/onboarding/onboarding-screen.js +161 -0
  82. package/dist/dashboard/web/onboarding/onboarding-screen.js.map +1 -0
  83. package/dist/dashboard/web/onboarding/onboarding-selection-store.d.ts +20 -0
  84. package/dist/dashboard/web/onboarding/onboarding-selection-store.js +76 -0
  85. package/dist/dashboard/web/onboarding/onboarding-selection-store.js.map +1 -0
  86. package/dist/dashboard/web/onboarding/product-copy.d.ts +40 -0
  87. package/dist/dashboard/web/onboarding/product-copy.js +70 -0
  88. package/dist/dashboard/web/onboarding/product-copy.js.map +1 -0
  89. package/dist/dashboard/web/onboarding/use-install-dwell.d.ts +22 -0
  90. package/dist/dashboard/web/onboarding/use-install-dwell.js +37 -0
  91. package/dist/dashboard/web/onboarding/use-install-dwell.js.map +1 -0
  92. package/dist/dashboard/web/onboarding/use-onboarding-token.d.ts +9 -0
  93. package/dist/dashboard/web/onboarding/use-onboarding-token.js +34 -0
  94. package/dist/dashboard/web/onboarding/use-onboarding-token.js.map +1 -0
  95. package/dist/dashboard/web/route-daemon-owner.d.ts +7 -0
  96. package/dist/dashboard/web/route-daemon-owner.js +15 -0
  97. package/dist/dashboard/web/route-daemon-owner.js.map +1 -0
  98. package/dist/dashboard/web/wire.d.ts +1 -1
  99. package/dist/shared/onboarding-types.d.ts +79 -0
  100. package/dist/shared/onboarding-types.js +12 -0
  101. package/dist/shared/onboarding-types.js.map +1 -0
  102. package/dist/telemetry/emit.d.ts +30 -3
  103. package/dist/telemetry/emit.js +25 -2
  104. package/dist/telemetry/emit.js.map +1 -1
  105. package/dist/telemetry/onboarding-session-ledger.d.ts +31 -0
  106. package/dist/telemetry/onboarding-session-ledger.js +78 -0
  107. package/dist/telemetry/onboarding-session-ledger.js.map +1 -0
  108. package/package.json +1 -1
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The `/onboarding` route's WIRE CONTRACT, PRD-009b, mirroring the daemon-side installer service
3
+ * from PRD-009a (`src/daemon/installer/`). `src/shared/onboarding-types.ts` is being authored in
4
+ * parallel by the daemon-side agent; these are LOCAL types matching that contract's field names
5
+ * exactly (per the implementation brief), so a later integration pass can point imports at the
6
+ * shared module without changing a single field name.
7
+ *
8
+ * Every schema mirrors the `wire.ts` convention: zod at the boundary, every field `.catch()`-
9
+ * defaulted so a partial/malformed body degrades to a safe empty state rather than throwing into
10
+ * React (the onboarding screen is the FIRST thing a new operator sees: it must never white-screen).
11
+ */
12
+ import { z } from "zod";
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+ // Product identity
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ /** Every product the daemon reports detection for (hive is always present; it is the caller). */
17
+ export const ONBOARDING_PRODUCTS = ["honeycomb", "doctor", "hive", "nectar"];
18
+ /** The three INSTALLABLE products, in the fixed order the guided flow walks them (ob-AC-6). */
19
+ export const FIXED_PRODUCT_ORDER = ["doctor", "honeycomb", "nectar"];
20
+ export function isInstallableProduct(value) {
21
+ return FIXED_PRODUCT_ORDER.includes(value);
22
+ }
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ // GET /api/onboarding/detect
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ export const PRODUCT_INSTALL_STATES = ["not_installed", "installed", "install_in_progress", "install_failed"];
27
+ const ProductErrorSchema = z.object({
28
+ stage: z.string().catch(""),
29
+ summary: z.string().catch(""),
30
+ });
31
+ const ProductDetectionSchema = z.object({
32
+ state: z.enum(PRODUCT_INSTALL_STATES).catch("not_installed"),
33
+ version: z.string().optional(),
34
+ error: ProductErrorSchema.optional(),
35
+ });
36
+ /** The safe default for a product the response omitted (never assume installed). */
37
+ export const DEFAULT_PRODUCT_DETECTION = Object.freeze({ state: "not_installed" });
38
+ // Every product key is OPTIONAL (a partial object, not a `z.record` over the enum): the daemon
39
+ // contract never guarantees all four keys ride every response, and "omitted" must degrade to
40
+ // {@link DEFAULT_PRODUCT_DETECTION} per-product rather than failing the WHOLE `products` object
41
+ // (which a zod `z.record` over a finite enum key would do, since that shape requires every key).
42
+ export const DetectResponseSchema = z.object({
43
+ products: z
44
+ .object({
45
+ honeycomb: ProductDetectionSchema.optional(),
46
+ doctor: ProductDetectionSchema.optional(),
47
+ hive: ProductDetectionSchema.optional(),
48
+ nectar: ProductDetectionSchema.optional(),
49
+ })
50
+ .catch({}),
51
+ });
52
+ /** The honest "nothing detected yet" default (a fetch failure never fabricates an installed product). */
53
+ export const EMPTY_DETECTION = Object.freeze({ products: {} });
54
+ /** Read one product's detection, defaulting to {@link DEFAULT_PRODUCT_DETECTION} when omitted. */
55
+ export function detectionFor(detection, product) {
56
+ return detection.products[product] ?? DEFAULT_PRODUCT_DETECTION;
57
+ }
58
+ /** ob-AC-3, every one of the four products reports `installed`. */
59
+ export function isFleetFullyInstalled(detection) {
60
+ return ONBOARDING_PRODUCTS.every((p) => detectionFor(detection, p).state === "installed");
61
+ }
62
+ /**
63
+ * The remaining (not-yet-installed) installable products, in the FIXED order, the set both the
64
+ * Standard flow (ob-AC-6, unfiltered) and the Advanced picker (ob-AC-7, pre-checked) start from.
65
+ */
66
+ export function remainingProducts(detection) {
67
+ return FIXED_PRODUCT_ORDER.filter((p) => detectionFor(detection, p).state !== "installed");
68
+ }
69
+ /**
70
+ * ob-AC-16/ob-AC-17, true when detection shows an install already under way or failed for one of
71
+ * the remaining products, meaning a choice (Standard or Advanced) was already made in a prior
72
+ * visit. The resumed queue is simply {@link remainingProducts} walked from the top: any product
73
+ * already `installed` is filtered out, and the first remaining entry is exactly the one that was
74
+ * mid-flight or failed (re-attached, never re-offered the hero/picker).
75
+ */
76
+ export function hasResumableInstall(detection) {
77
+ return remainingProducts(detection).some((p) => {
78
+ const state = detectionFor(detection, p).state;
79
+ return state === "install_in_progress" || state === "install_failed";
80
+ });
81
+ }
82
+ /**
83
+ * The queue to resume with (ob-AC-16), honoring the operator's original subset when it is known.
84
+ * `remainingProducts` alone would reinstall a product the operator explicitly DESELECTED in the
85
+ * Advanced picker, because the daemon has no server-side memory of that subset. When a persisted
86
+ * selection exists (see onboarding-selection-store), the resume queue is the remaining products
87
+ * intersected with it, so a deselected product is never silently reinstalled. When no selection is
88
+ * persisted (a fresh browser, cleared storage), any product that is genuinely mid-flight or failed
89
+ * is still resumed (an in-flight fact, not an assumption); a merely `not_installed` product with no
90
+ * persisted intent is left out, so the flow never installs something the operator did not choose.
91
+ */
92
+ export function buildResumeQueue(detection, persistedSelection) {
93
+ const remaining = remainingProducts(detection);
94
+ if (persistedSelection !== null) {
95
+ return remaining.filter((p) => persistedSelection.includes(p));
96
+ }
97
+ return remaining.filter((p) => {
98
+ const state = detectionFor(detection, p).state;
99
+ return state === "install_in_progress" || state === "install_failed";
100
+ });
101
+ }
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+ // POST /api/onboarding/install
104
+ // ─────────────────────────────────────────────────────────────────────────────
105
+ export const InstallStartResponseSchema = z.object({
106
+ product: z.enum(FIXED_PRODUCT_ORDER).catch("doctor"),
107
+ state: z.enum(["install_in_progress", "installed"]).catch("install_in_progress"),
108
+ });
109
+ /** `POST /api/onboarding/install` -> 409 refusal (is-AC-5). */
110
+ export const InstallRefusalResponseSchema = z.object({
111
+ error: z.enum(["unpublished", "manifest_unresolved"]),
112
+ });
113
+ /** Honest operator copy for installer refusals (409), with the same retry affordance as other failures. */
114
+ export function installRefusalMessage(error) {
115
+ if (error === "unpublished") {
116
+ return "This product is not published to npm yet, so the installer will not pull it. Retry after the release train publishes the package.";
117
+ }
118
+ return "The fleet version manifest could not be resolved, so the installer does not know which package version to install. Retry after the manifest is available.";
119
+ }
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+ // GET /api/onboarding/install/:product/events (SSE)
122
+ // ─────────────────────────────────────────────────────────────────────────────
123
+ /** The four REAL, observable install stages plus the two terminal outcomes (ob-AC-9: never a percent). */
124
+ export const INSTALL_STAGES = ["resolving", "downloading", "linking", "registering_service", "completed", "failed"];
125
+ /** The four IN-FLIGHT stages, in display order (excludes the two terminal outcomes). */
126
+ export const IN_FLIGHT_STAGES = ["resolving", "downloading", "linking", "registering_service"];
127
+ export const InstallProgressEventSchema = z.object({
128
+ stage: z.enum(INSTALL_STAGES).catch("resolving"),
129
+ detail: z.string().optional(),
130
+ });
131
+ /** The honest "daemon unreachable" default a failed/malformed health read degrades to. */
132
+ export const UNREACHABLE_HEALTH = Object.freeze({
133
+ ready: false,
134
+ status: { supervisor: "unreachable", daemons: [] },
135
+ });
136
+ /**
137
+ * Parse a `GET /api/onboarding/health` body leniently (mirrors `buzzing-screen.tsx`'s established
138
+ * `as FleetStatusResponse` cast for this exact daemon-owned union, {@link FleetStatusResponse} is
139
+ * a discriminated union keyed on `supervisor`, not a flat shape zod's object schemas model cleanly,
140
+ * and the existing precedent already trusts the daemon's own `isFleetReady`-validated projection).
141
+ */
142
+ export function parseHealthResponse(body) {
143
+ if (typeof body !== "object" || body === null)
144
+ return UNREACHABLE_HEALTH;
145
+ const record = body;
146
+ const ready = typeof record.ready === "boolean" ? record.ready : false;
147
+ const status = isFleetStatusShaped(record.status) ? record.status : UNREACHABLE_HEALTH.status;
148
+ return { ready, status };
149
+ }
150
+ function isFleetStatusShaped(value) {
151
+ if (typeof value !== "object" || value === null)
152
+ return false;
153
+ const supervisor = value.supervisor;
154
+ return supervisor === "reachable" || supervisor === "unreachable";
155
+ }
156
+ // ─────────────────────────────────────────────────────────────────────────────
157
+ // POST /api/onboarding/event, the UI funnel chokepoint (fire-and-forget, never awaited by callers)
158
+ // ─────────────────────────────────────────────────────────────────────────────
159
+ /** The exact UI-fired event names (product install + health events are daemon-side, never sent here). */
160
+ export const ONBOARDING_UI_EVENTS = ["onboarding_started", "mode_selected", "login_shown", "dashboard_reached"];
161
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../../../src/dashboard/web/onboarding/contracts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The GREEN-LIGHT HEALTH step, PRD-009b ob-AC-13. Polls `GET /api/onboarding/health` until the
3
+ * fleet reads ready, rendering one row per reported daemon using the SAME bee-state vocabulary
4
+ * `/buzzing` renders (`service-icons.tsx` / `service-status.ts`), so a health-conscious operator
5
+ * sees a consistent visual language across the whole onboarding-to-dashboard journey.
6
+ */
7
+ import React from "react";
8
+ import type { OnboardingClient } from "./onboarding-client.js";
9
+ export interface HealthViewProps {
10
+ readonly client: OnboardingClient;
11
+ /** Called once `ready:true` is observed. */
12
+ readonly onReady: () => void;
13
+ /** Overrides the poll interval (ms). Defaults to 1500, mirroring `/buzzing`'s dismissal poll. */
14
+ readonly pollMs?: number;
15
+ }
16
+ /** ob-AC-13, the fleet reads ready only once; this screen stops polling the instant it does. */
17
+ export declare function HealthView({ client, onReady, pollMs }: HealthViewProps): React.JSX.Element;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The GREEN-LIGHT HEALTH step, PRD-009b ob-AC-13. Polls `GET /api/onboarding/health` until the
4
+ * fleet reads ready, rendering one row per reported daemon using the SAME bee-state vocabulary
5
+ * `/buzzing` renders (`service-icons.tsx` / `service-status.ts`), so a health-conscious operator
6
+ * sees a consistent visual language across the whole onboarding-to-dashboard journey.
7
+ */
8
+ import React from "react";
9
+ import { deriveServiceState, fromFleetDaemonStatus } from "../../../shared/service-status.js";
10
+ import { SERVICE_STATE_COLOR, SERVICE_STATE_LABEL, ServiceStateIcon } from "../service-icons.js";
11
+ /** ob-AC-13, the fleet reads ready only once; this screen stops polling the instant it does. */
12
+ export function HealthView({ client, onReady, pollMs = 1500 }) {
13
+ const [daemons, setDaemons] = React.useState([]);
14
+ const [ready, setReady] = React.useState(false);
15
+ React.useEffect(() => {
16
+ if (ready)
17
+ return;
18
+ let alive = true;
19
+ let inFlight = false;
20
+ const tick = async () => {
21
+ if (inFlight)
22
+ return;
23
+ inFlight = true;
24
+ try {
25
+ const result = await client.health();
26
+ if (!alive)
27
+ return;
28
+ setDaemons(result.status.supervisor === "reachable" ? result.status.daemons : []);
29
+ if (result.ready)
30
+ setReady(true);
31
+ }
32
+ finally {
33
+ inFlight = false;
34
+ }
35
+ };
36
+ void tick();
37
+ const id = setInterval(() => void tick(), pollMs);
38
+ return () => {
39
+ alive = false;
40
+ clearInterval(id);
41
+ };
42
+ }, [ready, pollMs, client]);
43
+ React.useEffect(() => {
44
+ if (ready)
45
+ onReady();
46
+ }, [ready, onReady]);
47
+ return (_jsx("div", { "data-testid": "onboarding-health-view", style: {
48
+ display: "flex",
49
+ alignItems: "center",
50
+ justifyContent: "center",
51
+ minHeight: "100vh",
52
+ padding: 32,
53
+ background: "var(--bg-canvas)",
54
+ textAlign: "center",
55
+ }, children: _jsxs("div", { style: {
56
+ display: "flex",
57
+ flexDirection: "column",
58
+ alignItems: "center",
59
+ gap: 22,
60
+ width: "100%",
61
+ maxWidth: 480,
62
+ padding: "40px 36px",
63
+ background: "var(--bg-surface)",
64
+ border: "1px solid var(--border-default)",
65
+ borderRadius: "var(--radius-xl)",
66
+ }, children: [_jsx("h1", { style: { fontSize: "var(--text-2xl)", fontWeight: 700, color: "var(--text-primary)", margin: 0, letterSpacing: "-0.02em" }, children: "Bringing the fleet up green" }), _jsx("p", { style: { fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-secondary)", margin: 0, lineHeight: 1.5 }, children: "One last check before your dashboard: every daemon needs to report healthy." }), _jsx("ul", { role: "status", "aria-live": "polite", style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 8, width: "100%", textAlign: "left" }, children: daemons.length === 0 ? (_jsx("li", { style: { fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-tertiary)" }, children: "Checking the fleet\u2026" })) : (daemons.map((daemon) => {
67
+ const state = deriveServiceState({ signal: fromFleetDaemonStatus(daemon), now: Date.now(), firstActiveAt: null });
68
+ return (_jsxs("li", { "data-testid": `onboarding-health-row-${daemon.name}`, "data-state": state, style: {
69
+ display: "flex",
70
+ alignItems: "center",
71
+ gap: 12,
72
+ padding: "10px 14px",
73
+ borderRadius: "var(--radius-md)",
74
+ border: "1px solid var(--border-subtle)",
75
+ background: "var(--bg-elevated)",
76
+ }, children: [_jsx("span", { style: { color: SERVICE_STATE_COLOR[state], display: "inline-flex", flex: "none" }, children: _jsx(ServiceStateIcon, { state: state }) }), _jsx("span", { style: { flex: 1, fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-primary)" }, children: daemon.name }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-secondary)" }, children: SERVICE_STATE_LABEL[state] })] }, daemon.name));
77
+ })) })] }) }));
78
+ }
79
+ //# sourceMappingURL=health-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-view.js","sourceRoot":"","sources":["../../../../src/dashboard/web/onboarding/health-view.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The per-product GUIDED INSTALL CARD, PRD-009b ob-AC-8/ob-AC-9/ob-AC-10/ob-AC-11/ob-AC-12. A
3
+ * full-screen card carrying the product's logo, title, benefit copy, staged (never percent)
4
+ * progress, and the npm-safety reassurance. Honors a minimum ~30s dwell on success (ob-AC-11) and
5
+ * NEVER masks a failure behind that dwell (ob-AC-12), a `failed` stage renders its truthful error
6
+ * plus a retry affordance the instant it arrives, regardless of elapsed dwell time.
7
+ *
8
+ * Re-entry (ob-AC-16/ob-AC-17): `initialDetection` (the daemon's own truth from `/api/onboarding/
9
+ * detect`) decides the card's opening move, `install_in_progress` RE-ATTACHES the SSE stream
10
+ * without a new install POST; `install_failed` renders the failure immediately and waits for an
11
+ * explicit retry; anything else starts a fresh install.
12
+ */
13
+ import React from "react";
14
+ import { type InstallableProduct, type ProductDetection } from "./contracts.js";
15
+ import type { OnboardingClient } from "./onboarding-client.js";
16
+ export interface InstallCardProps {
17
+ readonly product: InstallableProduct;
18
+ /** The daemon's detection truth for THIS product at the moment the card mounted (ob-AC-16). */
19
+ readonly initialDetection: ProductDetection;
20
+ readonly client: OnboardingClient;
21
+ readonly assetBase: string;
22
+ /** Called once this product's install has both completed AND satisfied its minimum dwell. */
23
+ readonly onAdvance: () => void;
24
+ /** Overrides the default ~30s minimum dwell (ob-AC-11). A test injects a short window. */
25
+ readonly minDwellMs?: number;
26
+ }
27
+ export declare function InstallCard({ product, initialDetection, client, assetBase, onAdvance, minDwellMs }: InstallCardProps): React.JSX.Element;
@@ -0,0 +1,170 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The per-product GUIDED INSTALL CARD, PRD-009b ob-AC-8/ob-AC-9/ob-AC-10/ob-AC-11/ob-AC-12. A
4
+ * full-screen card carrying the product's logo, title, benefit copy, staged (never percent)
5
+ * progress, and the npm-safety reassurance. Honors a minimum ~30s dwell on success (ob-AC-11) and
6
+ * NEVER masks a failure behind that dwell (ob-AC-12), a `failed` stage renders its truthful error
7
+ * plus a retry affordance the instant it arrives, regardless of elapsed dwell time.
8
+ *
9
+ * Re-entry (ob-AC-16/ob-AC-17): `initialDetection` (the daemon's own truth from `/api/onboarding/
10
+ * detect`) decides the card's opening move, `install_in_progress` RE-ATTACHES the SSE stream
11
+ * without a new install POST; `install_failed` renders the failure immediately and waits for an
12
+ * explicit retry; anything else starts a fresh install.
13
+ */
14
+ import React from "react";
15
+ import { IN_FLIGHT_STAGES, installRefusalMessage } from "./contracts.js";
16
+ import { INSTALL_STAGE_LABEL, NPM_SAFETY_COPY, PRODUCT_COPY, productLogoUrl } from "./product-copy.js";
17
+ import { useInstallDwell } from "./use-install-dwell.js";
18
+ function stageStepState(stepIndex, doneCount, terminal) {
19
+ if (stepIndex < doneCount)
20
+ return "done";
21
+ if (stepIndex === doneCount && terminal !== "completed")
22
+ return "active";
23
+ return "pending";
24
+ }
25
+ /** The per-stage stepper (ob-AC-9): a labeled list, never a fabricated percentage. */
26
+ function StageStepper({ product, stage }) {
27
+ const currentIndex = IN_FLIGHT_STAGES.indexOf(stage);
28
+ const doneCount = stage === "completed" ? IN_FLIGHT_STAGES.length : Math.max(0, currentIndex);
29
+ return (_jsx("ul", { "data-testid": `onboarding-install-stage-${product}`, "data-current-stage": stage, style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 8, width: "100%", textAlign: "left" }, children: IN_FLIGHT_STAGES.map((step, i) => {
30
+ const state = stageStepState(i, doneCount, stage);
31
+ return (_jsxs("li", { "data-state": state, style: {
32
+ display: "flex",
33
+ alignItems: "center",
34
+ gap: 10,
35
+ fontFamily: "var(--font-sans)",
36
+ fontSize: "var(--text-sm)",
37
+ color: state === "pending" ? "var(--text-tertiary)" : "var(--text-primary)",
38
+ }, children: [_jsx("span", { "aria-hidden": "true", style: {
39
+ width: 8,
40
+ height: 8,
41
+ flex: "none",
42
+ borderRadius: "50%",
43
+ background: state === "done" ? "var(--verified)" : state === "active" ? "var(--honey)" : "var(--border-strong)",
44
+ animation: state === "active" ? "hc-onboarding-stage-pulse var(--dur-pollinate) var(--ease-in-out) infinite alternate" : "none",
45
+ } }), INSTALL_STAGE_LABEL[step]] }, step));
46
+ }) }));
47
+ }
48
+ /** ob-AC-10, the npm-safety reassurance, verbatim, on every card. */
49
+ function NpmSafetyNote({ product }) {
50
+ return (_jsx("p", { "data-testid": `onboarding-npm-safety-${product}`, style: {
51
+ fontFamily: "var(--font-sans)",
52
+ fontSize: "var(--text-xs)",
53
+ color: "var(--text-tertiary)",
54
+ margin: 0,
55
+ lineHeight: 1.5,
56
+ padding: "10px 14px",
57
+ background: "var(--bg-inset)",
58
+ border: "1px solid var(--border-subtle)",
59
+ borderRadius: "var(--radius-md)",
60
+ }, children: NPM_SAFETY_COPY }));
61
+ }
62
+ export function InstallCard({ product, initialDetection, client, assetBase, onAdvance, minDwellMs }) {
63
+ // Captured ONCE at mount, later prop identity changes (a parent re-render passing an
64
+ // equivalent-but-new object) must never re-trigger the opening-move decision below.
65
+ const openingRef = React.useRef(initialDetection);
66
+ const wasInitiallyInstalled = openingRef.current.state === "installed";
67
+ const wasInitiallyFailed = openingRef.current.state === "install_failed";
68
+ const [stage, setStage] = React.useState(() => {
69
+ if (wasInitiallyInstalled)
70
+ return "completed";
71
+ if (wasInitiallyFailed)
72
+ return "failed";
73
+ return "resolving";
74
+ });
75
+ const [error, setError] = React.useState(() => openingRef.current.error ?? null);
76
+ // Bumped by the retry button; also doubles as "has the operator asked us to try again" so a
77
+ // card that OPENED failed runs no network activity until this leaves 0 (ob-AC-12).
78
+ const [attempt, setAttempt] = React.useState(0);
79
+ React.useEffect(() => {
80
+ if (wasInitiallyInstalled)
81
+ return;
82
+ if (wasInitiallyFailed && attempt === 0)
83
+ return;
84
+ let cancelled = false;
85
+ let unsubscribe = () => { };
86
+ void (async () => {
87
+ // ob-AC-17: re-attach ONLY on the very first attempt of a card that opened mid-flight ,
88
+ // every retry (attempt > 0) always re-POSTs a fresh install, even for that same product.
89
+ const reattachOnly = attempt === 0 && openingRef.current.state === "install_in_progress";
90
+ if (!reattachOnly) {
91
+ const started = await client.startInstall(product);
92
+ if (cancelled)
93
+ return;
94
+ if (started === null) {
95
+ setError({ stage: "resolving", summary: "Could not reach the daemon to start the install. Check your connection and retry." });
96
+ setStage("failed");
97
+ return;
98
+ }
99
+ if ("error" in started) {
100
+ setError({ stage: "resolving", summary: installRefusalMessage(started.error) });
101
+ setStage("failed");
102
+ return;
103
+ }
104
+ if (started.state === "installed") {
105
+ // A race the daemon itself resolved before this card's POST landed, still honors
106
+ // the minimum dwell below, just skips the SSE stream entirely (nothing to stream).
107
+ setStage("completed");
108
+ return;
109
+ }
110
+ }
111
+ setError(null);
112
+ unsubscribe = client.subscribeInstallEvents(product, (event) => {
113
+ if (cancelled)
114
+ return;
115
+ setStage(event.stage);
116
+ if (event.stage === "failed") {
117
+ setError({ stage: "unknown", summary: event.detail ?? "The install failed. Retry below, or check the daemon logs." });
118
+ }
119
+ });
120
+ })();
121
+ return () => {
122
+ cancelled = true;
123
+ unsubscribe();
124
+ };
125
+ // `product`/`client` are stable for a card's lifetime; `attempt` is the sole re-run trigger.
126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
127
+ }, [attempt, wasInitiallyFailed, wasInitiallyInstalled]);
128
+ const handleRetry = React.useCallback(() => {
129
+ setError(null);
130
+ setStage("resolving");
131
+ setAttempt((n) => n + 1);
132
+ }, []);
133
+ // ob-AC-11: gates ONLY the success-advance path. A `failed` stage is rendered above regardless
134
+ // of dwell, this hook never sees `ready:true` for a failed card, so it can never mask one.
135
+ useInstallDwell(stage === "completed", { onDwellSatisfied: onAdvance, minDwellMs });
136
+ const copy = PRODUCT_COPY[product];
137
+ const failed = stage === "failed";
138
+ return (_jsxs("div", { "data-testid": `onboarding-install-card-${product}`, "data-stage": stage, style: {
139
+ display: "flex",
140
+ alignItems: "center",
141
+ justifyContent: "center",
142
+ minHeight: "100vh",
143
+ padding: 32,
144
+ background: "var(--bg-canvas)",
145
+ textAlign: "center",
146
+ }, children: [_jsxs("div", { style: {
147
+ display: "flex",
148
+ flexDirection: "column",
149
+ alignItems: "center",
150
+ gap: 22,
151
+ width: "100%",
152
+ maxWidth: 540,
153
+ padding: "44px 40px",
154
+ background: "var(--bg-surface)",
155
+ border: "1px solid var(--border-default)",
156
+ borderRadius: "var(--radius-xl)",
157
+ }, children: [_jsx("img", { src: productLogoUrl(product, assetBase), width: 64, height: 64, alt: "" }), _jsx("h1", { style: { fontSize: "var(--text-2xl)", fontWeight: 700, color: "var(--text-primary)", margin: 0, letterSpacing: "-0.02em" }, children: copy.title }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6, maxWidth: 440 }, children: [_jsx("p", { style: { fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", fontWeight: 600, color: "var(--text-primary)", margin: 0 }, children: copy.headline }), _jsx("p", { style: { fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-secondary)", margin: 0, lineHeight: 1.5 }, children: copy.lines[0] }), _jsx("p", { style: { fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-secondary)", margin: 0, lineHeight: 1.5 }, children: copy.lines[1] })] }), failed ? (_jsxs("div", { "data-testid": `onboarding-install-error-${product}`, style: { display: "flex", flexDirection: "column", gap: 12, alignItems: "center", width: "100%" }, children: [_jsx("p", { style: { fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--severity-critical)", margin: 0 }, children: error?.summary ?? "The install failed." }), _jsx("button", { type: "button", "data-testid": `onboarding-retry-${product}`, onClick: handleRetry, style: {
158
+ height: 40,
159
+ padding: "0 18px",
160
+ borderRadius: "var(--radius-md)",
161
+ border: "1px solid var(--severity-critical)",
162
+ background: "var(--severity-critical-bg)",
163
+ color: "var(--severity-critical)",
164
+ fontFamily: "var(--font-sans)",
165
+ fontSize: "var(--text-sm)",
166
+ fontWeight: 600,
167
+ cursor: "pointer",
168
+ }, children: "Retry" })] })) : (_jsx(StageStepper, { product: product, stage: stage })), _jsx(NpmSafetyNote, { product: product })] }), _jsx("style", { children: "@keyframes hc-onboarding-stage-pulse { from { opacity: .4 } to { opacity: 1 } }" })] }));
169
+ }
170
+ //# sourceMappingURL=install-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-card.js","sourceRoot":"","sources":["../../../../src/dashboard/web/onboarding/install-card.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The onboarding LOGIN STEP, PRD-009b ob-AC-14/ob-AC-15. Closes the known device-code display gap
3
+ * by reusing the EXACT wire contract `GuidedSetup` (`src/dashboard/web/setup-gate.tsx`) already
4
+ * defines (`POST /setup/login`, polled `GET /setup/state`) WITHOUT modifying that module: this is
5
+ * the sibling component the task brief calls for, sharing `wire.ts`'s types/client rather than
6
+ * duplicating the device-flow request shapes.
7
+ *
8
+ * Unlike `/login`'s `GuidedSetup` (which waits for an explicit "First time setup" click), the
9
+ * onboarding flow has already walked the operator through installs and a health check, so this
10
+ * step begins the device flow automatically on mount: one fewer click at the end of a long guided
11
+ * sequence. Once `/setup/state.authenticated` flips true it fires `dashboard_reached`, best-effort
12
+ * POSTs `/api/onboarding/complete`, then hard-navigates to `/` (ob-AC-15) so the server gate
13
+ * revalidates and serves the authoritative dashboard, the same discipline `LoginScreen` follows.
14
+ */
15
+ import React from "react";
16
+ import { type WireClient } from "../wire.js";
17
+ import type { OnboardingClient } from "./onboarding-client.js";
18
+ /** Mirrors `setup-gate.tsx`'s `SETUP_POLL_MS`, the live-transition poll cadence. */
19
+ export declare const LOGIN_STEP_POLL_MS: 2500;
20
+ export interface LoginStepProps {
21
+ readonly onboardingClient: OnboardingClient;
22
+ /** The proxied setup wire client (defaults to the live one; a test injects a mock). */
23
+ readonly wire?: WireClient;
24
+ /** Test seam: called instead of the real hard navigation once authenticated. */
25
+ readonly onAuthenticated?: () => void;
26
+ /** Overrides {@link LOGIN_STEP_POLL_MS} (a test injects a short window). */
27
+ readonly pollMs?: number;
28
+ }
29
+ export declare function LoginStep({ onboardingClient, wire: wireOverride, onAuthenticated, pollMs }: LoginStepProps): React.JSX.Element;