foundry-design-web-adapter 0.2.0-beta.3 → 0.2.0-beta.5

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/README.md CHANGED
@@ -4,27 +4,51 @@ Foundry is a local-first precision design workbench for Codex, Cursor, and Claud
4
4
 
5
5
  ## Beta installation
6
6
 
7
- Foundry is distributed through npm, so testers do not need access to the private GitHub repository. From the project you want to inspect, run:
7
+ Foundry is distributed through npm, so testers do not need GitHub access.
8
+
9
+ ### Recommended: let your coding agent install it
10
+
11
+ Open the project in Codex, Cursor, or Claude Code and paste:
12
+
13
+ ```text
14
+ Install Foundry for this project by running npx foundry-design@beta setup --yes. Check the result and tell me when I need to restart.
15
+ ```
16
+
17
+ Setup detects the project and active coding agent, installs development-only instrumentation, configures the local agent connection, validates the integration, and records everything it owns for safe updates or removal.
18
+
19
+ Restart the coding agent once, reopen the same project folder, and paste:
20
+
21
+ ```text
22
+ Start Foundry for this project and keep listening for Apply with agent requests.
23
+ ```
24
+
25
+ That is the normal workflow. Foundry starts the project when needed, opens an authenticated local preview, and shows **Agent is ready** before it allows an apply request.
26
+
27
+ ### Manual installation
28
+
29
+ From the project you want to inspect, run:
8
30
 
9
31
  ```bash
10
32
  npx foundry-design@beta setup
11
33
  ```
12
34
 
13
- Setup detects the platform, framework, package manager, development command, and installed coding agents. It previews every file it will manage, adds development-only instrumentation for supported web frameworks, installs the Foundry skill for Codex, Cursor, or Claude Code, configures the selected MCP clients, and records a reversible install manifest.
14
-
15
- Restart the coding agent after setup, then ask:
35
+ Then restart the coding agent, reopen the same project folder, and ask:
16
36
 
17
37
  ```text
18
- Start Foundry for this project.
38
+ Start Foundry for this project and keep listening for Apply with agent requests.
19
39
  ```
20
40
 
21
- Then start a session with:
41
+ You do not need to run a second terminal command when the agent starts Foundry for you. For a manual-only preview, run `npx foundry-design@beta start`, but Apply with agent still requires a restarted and actively listening coding agent.
42
+
43
+ Update an existing installation with:
22
44
 
23
45
  ```bash
24
- npx foundry-design@beta start
46
+ npx foundry-design@beta update
25
47
  ```
26
48
 
27
- Foundry reuses an available project server or starts the detected development command, launches the loopback runtime, and opens an authenticated visual preview. Remove only Foundry-managed integration with:
49
+ The updater refreshes checksum-matched Foundry files, agent connections, and skill bundles. It preserves and reports files changed since setup, validates the project, and rolls back the update if Foundry introduces a TypeScript or lint failure. Restart the coding agent after updating.
50
+
51
+ Remove only Foundry-managed integration with:
28
52
 
29
53
  ```bash
30
54
  npx foundry-design@beta uninstall
package/dist/adapter.js CHANGED
@@ -896,6 +896,25 @@ var FOUNDRY_UI_FOUNDATION_CSS = `
896
896
  }
897
897
  `;
898
898
 
899
+ // src/diagnostics.ts
900
+ function createSafeDiagnostics(input) {
901
+ return {
902
+ product: "Foundry Design Control",
903
+ protocolVersion: input.protocolVersion ?? "1.1.0",
904
+ interfaceTheme: input.interfaceTheme,
905
+ connection: {
906
+ runtime: input.runtimeConnected ? "connected" : "disconnected",
907
+ agent: input.agentConnected ? "connected" : "disconnected",
908
+ ...input.agentConnected && input.agentName ? { agentName: input.agentName } : {}
909
+ },
910
+ workspace: {
911
+ selectedElementCount: Math.max(0, input.selectedCount),
912
+ recordedChangeCount: Math.max(0, input.recordedChangeCount),
913
+ latestApplyState: input.latestApplyState ?? "none"
914
+ }
915
+ };
916
+ }
917
+
899
918
  // src/workspace.ts
900
919
  function resolveInterfaceTheme(preference, systemPrefersDark) {
901
920
  return preference === "system" ? systemPrefersDark ? "dark" : "light" : preference;
@@ -1197,6 +1216,8 @@ var PANEL_CSS = `
1197
1216
  .review-head strong { font-size:12px; }.review-summary { padding:12px 16px 12px;border-bottom:1px solid var(--fdc-line);background:#fcfcfc; }.review-summary strong { display:block;font-size:12px;font-weight:550;letter-spacing:-.01em; }.review-summary span { display:block;margin-top:4px;color:var(--fdc-muted);font-size:12px;line-height:1.45; }.review-summary.attention strong { color:#8b4d3d; }.review-toolbar button { height:28px;padding:0 8px;font-size:12px; }
1198
1217
  .onboarding-card { bottom:84px;width:340px;padding:16px;border-radius:12px;box-shadow:0 16px 40px rgb(0 0 0 / 15%); }.onboarding-card-head { gap:8px; }.onboarding-card-head span { width:28px;height:28px;display:grid;place-items:center;color:#a75031;background:#fbf1ed;border-radius:8px; }.onboarding-card-head svg { width:16px;height:16px;color:inherit; }.onboarding-card-head strong { font-size:12px;letter-spacing:-.015em; }.onboarding-card>p { margin:8px 0 16px;font-size:12px;line-height:1.55; }.onboarding-steps { display:grid;grid-template-columns:1fr;gap:1px;border:1px solid var(--fdc-line);border-radius:8px;overflow:hidden;background:var(--fdc-line); }.onboarding-step { display:grid;grid-template-columns:24px 1fr;gap:8px;padding:8px 12px;background:white;text-align:left; }.onboarding-step b { color:#8a8a8a;font-size:8px;font-weight:500;letter-spacing:.04em; }.onboarding-step strong { display:block;font-size:12px;font-weight:550; }.onboarding-step small { display:block;margin-top:4px;color:var(--fdc-muted);font-size:8px;line-height:1.35; }.onboarding-actions { gap:8px;margin-top:12px; }.onboarding-actions button { height:32px;padding:0 12px;border-radius:8px;font-size:12px; }
1199
1218
  .onboarding-card,.onboarding-step { color:var(--fdc-ink);background:var(--fdc-surface); }.onboarding-card-head span { color:#e7a68c;background:#36231d; }.onboarding-actions button { color:var(--fdc-ink);background:var(--fdc-elevated);border-color:var(--fdc-line); }.onboarding-actions .onboarding-start { color:#141416;background:#f0f1f3;border-color:#f0f1f3; }
1219
+ .status-popover { width:256px; }.status-popover-actions { display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-top:8px; }.status-popover .status-popover-actions button { height:32px;margin:0; }.status-popover .status-popover-actions [data-status-retry] { grid-column:1/-1; }
1220
+ .onboarding-card { width:360px; }.onboarding-card-head>strong { flex:1; }.onboarding-card-head .onboarding-close { width:28px;height:28px;display:grid;place-items:center;padding:0;border:0;border-radius:4px;background:transparent;color:var(--fdc-muted);cursor:pointer; }.onboarding-card-head .onboarding-close:hover { color:var(--fdc-ink);background:var(--fdc-subtle); }.onboarding-card-head .onboarding-close svg { width:12px;height:12px; }.onboarding-steps { gap:4px;border:0;overflow:visible;background:transparent; }.onboarding-step { min-height:40px;grid-template-columns:24px minmax(0,1fr);align-items:center;gap:8px;padding:8px;border:1px solid var(--fdc-line);border-radius:8px; }.onboarding-step b { width:20px;height:20px;display:grid;place-items:center;border:1px solid var(--fdc-line);border-radius:50%;color:var(--fdc-muted);font:500 8px/1 var(--fdc-font);letter-spacing:0; }.onboarding-step b svg { width:12px;height:12px; }.onboarding-step.complete>b { color:#141416;background:#8ae0c2;border-color:#8ae0c2; }.onboarding-step.current { border-color:var(--fdc-signal);box-shadow:0 0 0 4px rgb(59 130 246 / 10%); }
1200
1221
  .review-summary { color:var(--fdc-ink);background:var(--fdc-paper); }.empty-state-icon { color:#9ec5ff;background:var(--fdc-signal-soft); }
1201
1222
  .component-card,.health-card { color:var(--fdc-ink);background:var(--fdc-surface);border-color:var(--fdc-line); }.component-card.selected { color:var(--fdc-ink);background:var(--fdc-component-soft);border-color:#5c4b9e;box-shadow:0 0 0 4px rgb(155 135 245 / 10%); }.component-variants span,.component-actions button,.component-variants button { color:#c1b5f5;background:var(--fdc-component-soft);border-color:#4a406f; }.health-score::before { background:var(--fdc-surface); }.health-filters button { color:var(--fdc-muted); }.health-filters button:hover,.health-filters button.active { color:var(--fdc-ink);background:var(--fdc-elevated);border-color:var(--fdc-line); }.health-card p,.health-evidence { color:var(--fdc-muted); }.mapping-chooser { color:#f0c9a8;background:#35291d;border-color:#665039; }.mapping-chooser>strong,.mapping-option,.mapping-option small { color:#e7c29f; }.review-card.locating { background:var(--fdc-signal-soft); }.baseline-badge { color:#83d8bb;background:#18352c; }
1202
1223
  /* Workspace ownership */
@@ -1264,6 +1285,14 @@ var PANEL_CSS = `
1264
1285
  .review-overview .review-toolbar { position:static;z-index:auto;flex:none;padding:0;border:0;background:transparent;backdrop-filter:none; }
1265
1286
  .review-overview .review-toolbar button { height:28px;padding:0 8px;border-color:transparent;background:transparent;color:var(--fdc-muted);font-size:8px; }
1266
1287
  .review-overview .review-toolbar button:hover { color:var(--fdc-ink);background:var(--fdc-elevated);border-color:var(--fdc-line); }
1288
+ .review-agent { display:grid;grid-template-columns:8px minmax(0,1fr) auto;align-items:center;gap:12px;margin:12px 16px 0;padding:12px;border:1px solid var(--fdc-line);border-radius:8px;background:var(--fdc-paper); }
1289
+ .review-agent>i { width:8px;height:8px;border-radius:50%;background:#d16d51; }
1290
+ .review-agent.connected>i { background:#2ca67f;box-shadow:0 0 0 4px rgb(44 166 127 / 12%); }
1291
+ .review-agent strong,.review-agent span { display:block; }
1292
+ .review-agent strong { font-size:12px;font-weight:600; }
1293
+ .review-agent span { margin-top:4px;color:var(--fdc-muted);font-size:12px;line-height:1.45; }
1294
+ .review-agent button { height:32px;padding:0 12px;border:1px solid var(--fdc-line);border-radius:8px;background:var(--fdc-elevated);color:var(--fdc-ink);font-size:12px;cursor:pointer; }
1295
+ .review-agent button:hover { border-color:var(--fdc-muted); }
1267
1296
  .change-tray .review-group { border-right:0;border-bottom:1px solid var(--fdc-line); }
1268
1297
  .change-tray .review-group-title { position:sticky;top:52px;z-index:2;min-height:40px;padding:0 12px;background:rgb(28 28 31 / 98%); }
1269
1298
  .review-group-title .included-count { font-size:8px; }
@@ -1883,7 +1912,7 @@ function installFoundryInspector(options = {}) {
1883
1912
  statusPill.setAttribute("aria-label", "Foundry session status");
1884
1913
  statusPill.insertAdjacentHTML(
1885
1914
  "afterend",
1886
- '<div class="status-popover" hidden><strong data-status-title>Session connected</strong><span data-status-detail>Changes are stored locally.</span><code data-status-project></code><code data-status-revision></code><button data-status-retry>Check connection</button></div>'
1915
+ '<div class="status-popover" hidden><strong data-status-title>Session connected</strong><span data-status-detail>Changes are stored locally.</span><code data-status-project></code><code data-status-revision></code><div class="status-popover-actions"><button data-status-checklist>Getting started</button><button data-status-diagnostics>Copy diagnostics</button><button data-status-retry>Check connection</button></div></div>'
1887
1916
  );
1888
1917
  shadow.querySelector(".panel").insertAdjacentHTML(
1889
1918
  "beforeend",
@@ -1893,7 +1922,7 @@ function installFoundryInspector(options = {}) {
1893
1922
  onboarding.className = "onboarding-card";
1894
1923
  onboarding.hidden = true;
1895
1924
  onboarding.setAttribute("aria-label", "Getting started with Foundry");
1896
- onboarding.innerHTML = '<div class="onboarding-card-head"><span><i data-foundry-icon="sparkles"></i></span><strong>Start with the interface</strong></div><p>Foundry stays in Select mode while you work. Click anything, refine measured values, then apply one reviewed batch.</p><div class="onboarding-steps"><div class="onboarding-step"><b>01</b><span><strong>Select</strong><small>Click the page or choose a layer.</small></span></div><div class="onboarding-step"><b>02</b><span><strong>Refine</strong><small>Adjust real layout, type, color, and motion.</small></span></div><div class="onboarding-step"><b>03</b><span><strong>Apply</strong><small>Review once, update source, and verify.</small></span></div></div><div class="onboarding-actions"><button class="onboarding-shortcuts">View shortcuts</button><button class="onboarding-start">Select an element</button></div>';
1925
+ onboarding.innerHTML = '<div class="onboarding-card-head"><span><i data-foundry-icon="sparkles"></i></span><strong>Getting started</strong><button class="onboarding-close" aria-label="Close getting started"><i data-foundry-icon="x"></i></button></div><p>Complete one real design loop. Progress stays in this browser and contains no project content.</p><div class="onboarding-steps"></div><div class="onboarding-actions"><button class="onboarding-shortcuts">View shortcuts</button><button class="onboarding-start">Continue</button></div>';
1897
1926
  shadow.append(onboarding);
1898
1927
  const compareBar = document.createElement("div");
1899
1928
  compareBar.className = "compare-bar";
@@ -2081,6 +2110,7 @@ function installFoundryInspector(options = {}) {
2081
2110
  let selectedControls = [];
2082
2111
  let resizeObserver;
2083
2112
  let activeReviewPayload = null;
2113
+ let activeAgentPresence = { connected: false, presence: null };
2084
2114
  const reviewDraftKey = `__foundry_review_draft:${sessionId || "local"}`;
2085
2115
  let reviewDraft = (() => {
2086
2116
  try {
@@ -2143,10 +2173,84 @@ function installFoundryInspector(options = {}) {
2143
2173
  const capturedBaselineRuns = /* @__PURE__ */ new Set();
2144
2174
  let projectRoot = location.origin;
2145
2175
  let projectRevision = "";
2176
+ let runtimeConnected = false;
2177
+ let recordedChangeCount = 0;
2178
+ let latestApplyState = "none";
2179
+ const onboardingCompleted = /* @__PURE__ */ new Set();
2146
2180
  let designMemory = emptyDesignMemory();
2147
2181
  let matrixMode = false;
2148
2182
  let lastUtilityTrigger = null;
2149
2183
  const utilityRects = /* @__PURE__ */ new Map();
2184
+ const onboardingSteps = [
2185
+ {
2186
+ id: "setup",
2187
+ title: "Setup complete",
2188
+ detail: "The project adapter and local session are ready."
2189
+ },
2190
+ {
2191
+ id: "agent",
2192
+ title: "Agent restarted and connected",
2193
+ detail: "Keep the coding agent listening while you design."
2194
+ },
2195
+ {
2196
+ id: "selection",
2197
+ title: "First element selected",
2198
+ detail: "Click the canvas or choose an item in Layers."
2199
+ },
2200
+ {
2201
+ id: "change",
2202
+ title: "First change recorded",
2203
+ detail: "Adjust a measured value in the Inspector."
2204
+ },
2205
+ {
2206
+ id: "apply",
2207
+ title: "First batch applied and verified",
2208
+ detail: "Review once, update source, then measure the rebuild."
2209
+ }
2210
+ ];
2211
+ function onboardingStorageKey(suffix) {
2212
+ return `__foundry_getting_started:${suffix}:${projectRoot}`;
2213
+ }
2214
+ function loadOnboardingProgress() {
2215
+ onboardingCompleted.clear();
2216
+ try {
2217
+ const stored = JSON.parse(localStorage.getItem(onboardingStorageKey("completed")) ?? "[]");
2218
+ for (const id of stored) {
2219
+ if (onboardingSteps.some((step) => step.id === id)) onboardingCompleted.add(id);
2220
+ }
2221
+ } catch {
2222
+ localStorage.removeItem(onboardingStorageKey("completed"));
2223
+ }
2224
+ }
2225
+ function completeOnboardingStep(id) {
2226
+ if (onboardingCompleted.has(id)) return;
2227
+ onboardingCompleted.add(id);
2228
+ try {
2229
+ localStorage.setItem(
2230
+ onboardingStorageKey("completed"),
2231
+ JSON.stringify([...onboardingCompleted])
2232
+ );
2233
+ } catch {
2234
+ }
2235
+ renderOnboardingChecklist();
2236
+ }
2237
+ function renderOnboardingChecklist(forceOpen = false) {
2238
+ const nextStep = onboardingSteps.find((step) => !onboardingCompleted.has(step.id));
2239
+ const complete = !nextStep;
2240
+ const stepsRoot = onboarding.querySelector(".onboarding-steps");
2241
+ stepsRoot.innerHTML = onboardingSteps.map((step, index) => {
2242
+ const done = onboardingCompleted.has(step.id);
2243
+ const current = step.id === nextStep?.id;
2244
+ return `<div class="onboarding-step ${done ? "complete" : ""} ${current ? "current" : ""}"><b>${done ? '<i data-foundry-icon="check"></i>' : String(index + 1).padStart(2, "0")}</b><span><strong>${step.title}</strong><small>${step.detail}</small></span></div>`;
2245
+ }).join("");
2246
+ renderIcons(stepsRoot);
2247
+ const continueButton = onboarding.querySelector(".onboarding-start");
2248
+ const action = nextStep?.id ?? "done";
2249
+ continueButton.dataset.action = action;
2250
+ continueButton.textContent = action === "agent" ? "Copy restart prompt" : action === "selection" ? "Select an element" : action === "change" ? "Adjust a value" : action === "apply" ? "Review changes" : "Done";
2251
+ const dismissed = localStorage.getItem(onboardingStorageKey("dismissed")) === "1";
2252
+ onboarding.hidden = complete || !forceOpen && dismissed;
2253
+ }
2150
2254
  function utilityStorageKey(utility) {
2151
2255
  return `__foundry_utility_rect:${utility}:${projectRoot}`;
2152
2256
  }
@@ -2380,16 +2484,23 @@ function installFoundryInspector(options = {}) {
2380
2484
  async function hydrateSession() {
2381
2485
  if (!sessionId || !token) return;
2382
2486
  try {
2383
- const response = await fetch(`${runtimeUrl}/v1/sessions/${sessionId}`, {
2384
- headers: { "x-foundry-token": token }
2385
- });
2487
+ const [response, presence] = await Promise.all([
2488
+ fetch(`${runtimeUrl}/v1/sessions/${sessionId}`, {
2489
+ headers: { "x-foundry-token": token }
2490
+ }),
2491
+ sessionRequest("/agent-presence").catch(() => ({ connected: false, presence: null }))
2492
+ ]);
2386
2493
  if (!response.ok) throw new Error("The local session could not be read.");
2387
- const { changeSet, designGraph: graph } = await response.json();
2494
+ const payload = await response.json();
2495
+ const { changeSet, designGraph: graph } = payload;
2496
+ runtimeConnected = true;
2497
+ activeAgentPresence = presence;
2388
2498
  statusPopover.querySelector("[data-status-project]").textContent = changeSet.context.projectRoot;
2389
2499
  statusPopover.querySelector("[data-status-revision]").textContent = `Revision ${changeSet.context.revision ?? "working tree"}`;
2390
2500
  const nextProjectRoot = changeSet.context.projectRoot || location.origin;
2391
2501
  if (projectRoot !== nextProjectRoot || !hydratedOnce) {
2392
2502
  projectRoot = nextProjectRoot;
2503
+ loadOnboardingProgress();
2393
2504
  designMemory = readDesignMemory(localStorage, projectRoot);
2394
2505
  utilityRects.clear();
2395
2506
  positionWorkspaceSurfaces();
@@ -2402,15 +2513,23 @@ function installFoundryInspector(options = {}) {
2402
2513
  const activeChanges = changeSet.changes.filter(
2403
2514
  (change) => change.status !== "rejected" && String(change.before) !== String(change.after)
2404
2515
  );
2516
+ recordedChangeCount = activeChanges.length;
2517
+ latestApplyState = payload.applyRuns?.at(-1)?.state ?? "none";
2405
2518
  updateChangeCount(activeChanges.length, activeChanges.at(-1));
2519
+ completeOnboardingStep("setup");
2520
+ if (activeAgentPresence.connected) completeOnboardingStep("agent");
2521
+ if (selectedElements.length) completeOnboardingStep("selection");
2522
+ if (activeChanges.length) completeOnboardingStep("change");
2523
+ if (latestApplyState === "passed") completeOnboardingStep("apply");
2524
+ renderOnboardingChecklist();
2406
2525
  setSessionStatus("live");
2407
2526
  if (!libraryPanel.hidden) renderDesignMemory();
2408
2527
  if (changeSet.changes.length === 0 && !hydratedOnce) {
2409
2528
  showToast("Click any element. Shift-click builds a selection.");
2410
- if (!localStorage.getItem("__foundry_onboarded")) onboarding.hidden = false;
2411
2529
  }
2412
2530
  hydratedOnce = true;
2413
2531
  } catch (error) {
2532
+ runtimeConnected = false;
2414
2533
  setSessionStatus(
2415
2534
  "error",
2416
2535
  error instanceof Error ? error.message : "The local session could not be read."
@@ -3394,8 +3513,29 @@ function installFoundryInspector(options = {}) {
3394
3513
  const count = group.querySelector(".included-count");
3395
3514
  if (count) count.textContent = `${included} included`;
3396
3515
  });
3397
- applyButton.textContent = selectedCount ? `Apply ${selectedCount} with agent` : "Apply with agent";
3398
- applyButton.disabled = selectedCount === 0;
3516
+ applyButton.textContent = !activeAgentPresence.connected ? "Connect agent to apply" : selectedCount ? `Apply ${selectedCount} with agent` : "Apply with agent";
3517
+ applyButton.disabled = selectedCount === 0 || !activeAgentPresence.connected;
3518
+ }
3519
+ function agentConnectionMarkup() {
3520
+ const agentName = activeAgentPresence.presence?.agent?.name;
3521
+ return `<div class="review-agent ${activeAgentPresence.connected ? "connected" : "disconnected"}" aria-live="polite"><i></i><div><strong>${activeAgentPresence.connected ? `${escapeHtml(agentName ?? "Coding agent")} is ready` : "Connect your coding agent"}</strong><span>${activeAgentPresence.connected ? "Apply requests will be claimed automatically while this agent keeps listening." : "In the same project, ask your agent to start Foundry and keep listening for Apply requests."}</span></div>${activeAgentPresence.connected ? "" : "<button data-copy-agent-listener>Copy instruction</button>"}</div>`;
3522
+ }
3523
+ function updateAgentConnection() {
3524
+ const current = reviewBody.querySelector(".review-agent");
3525
+ if (!current) {
3526
+ updateReviewSelection();
3527
+ return;
3528
+ }
3529
+ const replacement = document.createElement("div");
3530
+ replacement.innerHTML = agentConnectionMarkup();
3531
+ current.replaceWith(replacement.firstElementChild);
3532
+ reviewBody.querySelector("[data-copy-agent-listener]")?.addEventListener("click", () => {
3533
+ void navigator.clipboard.writeText(
3534
+ "Start Foundry for this project and keep listening for Apply with agent requests."
3535
+ );
3536
+ showToast("Agent instruction copied");
3537
+ });
3538
+ updateReviewSelection();
3399
3539
  }
3400
3540
  function renderReviewList(changes) {
3401
3541
  const groups = /* @__PURE__ */ new Map();
@@ -3409,7 +3549,7 @@ function installFoundryInspector(options = {}) {
3409
3549
  const unresolvedCount = changes.filter(
3410
3550
  (change) => change.confidence === "unresolved" || change.mappingCandidates?.length > 1 && !change.selectedMappingId
3411
3551
  ).length;
3412
- reviewBody.innerHTML = changes.length || rejectedCount ? `<div class="review-overview"><div class="review-summary" data-unresolved="${unresolvedCount}" aria-live="polite"><strong>Preparing review\u2026</strong><span>Only included, exactly mapped changes will be sent to your agent.</span></div><div class="review-toolbar"><button data-review-compare>Compare</button><button data-review-approve-exact>Include all</button><button data-review-toggle-rejected>${reviewShowRejected ? "Hide removed" : rejectedCount ? `${rejectedCount} removed` : "Removed"}</button></div></div>${[
3552
+ reviewBody.innerHTML = changes.length || rejectedCount ? `<div class="review-overview"><div class="review-summary" data-unresolved="${unresolvedCount}" aria-live="polite"><strong>Preparing review\u2026</strong><span>Only included, exactly mapped changes will be sent to your agent.</span></div><div class="review-toolbar"><button data-review-compare>Compare</button><button data-review-approve-exact>Include all</button><button data-review-toggle-rejected>${reviewShowRejected ? "Hide removed" : rejectedCount ? `${rejectedCount} removed` : "Removed"}</button></div></div>${agentConnectionMarkup()}${[
3413
3553
  ...groups.values()
3414
3554
  ].map((group) => {
3415
3555
  const target = group[0].target;
@@ -3443,6 +3583,12 @@ function installFoundryInspector(options = {}) {
3443
3583
  reviewCancel.dataset.action = "back";
3444
3584
  reviewCancel.textContent = "Back";
3445
3585
  renderIcons(reviewBody);
3586
+ reviewBody.querySelector("[data-copy-agent-listener]")?.addEventListener("click", () => {
3587
+ void navigator.clipboard.writeText(
3588
+ "Start Foundry for this project and keep listening for Apply with agent requests."
3589
+ );
3590
+ showToast("Agent instruction copied");
3591
+ });
3446
3592
  reviewBody.querySelectorAll("[data-review-change]").forEach(
3447
3593
  (field) => field.addEventListener("change", () => {
3448
3594
  reviewDraft.selections[field.dataset.reviewChange] = field.checked;
@@ -3603,6 +3749,8 @@ function installFoundryInspector(options = {}) {
3603
3749
  const passed = run.state === "passed";
3604
3750
  const active = ["queued", "claimed", "applying", "rebuilding", "verifying"].includes(run.state);
3605
3751
  const latestMessage = run.state === "queued" ? "The reviewed changes are queued. Keep your coding agent active, or ask it to apply your reviewed Foundry changes." : run.messages.at(-1)?.message ?? run.error ?? "Apply run created.";
3752
+ latestApplyState = run.state;
3753
+ if (run.state === "passed") completeOnboardingStep("apply");
3606
3754
  captureVerifiedRun(run);
3607
3755
  reviewCount.textContent = `Attempt ${run.attempts}`;
3608
3756
  reviewBody.innerHTML = `<div class="run-summary"><div class="run-state"><i class="${passed ? "passed" : attention ? "attention" : active ? "active" : ""}"></i><strong>${escapeHtml(runStateLabels[run.state] ?? run.state)}</strong></div><p>${escapeHtml(latestMessage)}</p></div><div class="run-steps">${run.messages.map(
@@ -3668,7 +3816,12 @@ function installFoundryInspector(options = {}) {
3668
3816
  async function refreshReview() {
3669
3817
  if (!workspaceState.reviewOpen || !sessionId || !token) return;
3670
3818
  try {
3671
- const payload = await sessionRequest();
3819
+ const [payload, presence] = await Promise.all([
3820
+ sessionRequest(),
3821
+ sessionRequest("/agent-presence")
3822
+ ]);
3823
+ activeAgentPresence = presence;
3824
+ if (presence.connected) completeOnboardingStep("agent");
3672
3825
  const latestRun = payload.applyRuns?.at(-1);
3673
3826
  const runNeedsRefresh = latestRun && [
3674
3827
  "queued",
@@ -3681,6 +3834,7 @@ function installFoundryInspector(options = {}) {
3681
3834
  ].includes(latestRun.state);
3682
3835
  if (applyButton.dataset.action === "apply" && !runNeedsRefresh) {
3683
3836
  activeReviewPayload = payload;
3837
+ updateAgentConnection();
3684
3838
  return;
3685
3839
  }
3686
3840
  renderReviewPayload(payload);
@@ -3702,7 +3856,13 @@ function installFoundryInspector(options = {}) {
3702
3856
  workspaceState = updateWorkspace(workspaceState, { type: "set-review", open: true });
3703
3857
  reviewTakeover.hidden = false;
3704
3858
  outline.hidden = true;
3705
- renderReviewPayload(await sessionRequest());
3859
+ const [payload, presence] = await Promise.all([
3860
+ sessionRequest(),
3861
+ sessionRequest("/agent-presence")
3862
+ ]);
3863
+ activeAgentPresence = presence;
3864
+ if (presence.connected) completeOnboardingStep("agent");
3865
+ renderReviewPayload(payload);
3706
3866
  requestAnimationFrame(() => {
3707
3867
  reviewBody.scrollTop = reviewScrollTop;
3708
3868
  shadow.querySelector(".review-back")?.focus();
@@ -3743,6 +3903,12 @@ function installFoundryInspector(options = {}) {
3743
3903
  );
3744
3904
  applyButton.disabled = true;
3745
3905
  try {
3906
+ activeAgentPresence = await sessionRequest("/agent-presence");
3907
+ if (!activeAgentPresence.connected) {
3908
+ updateAgentConnection();
3909
+ showToast("No coding agent is listening yet");
3910
+ return;
3911
+ }
3746
3912
  const payload = await sessionRequest("/apply-runs", {
3747
3913
  method: "POST",
3748
3914
  body: JSON.stringify({
@@ -3997,6 +4163,8 @@ function installFoundryInspector(options = {}) {
3997
4163
  const activeChanges = responsePayload.changeSet.changes.filter(
3998
4164
  (change) => change.status !== "rejected" && String(change.before) !== String(change.after)
3999
4165
  );
4166
+ recordedChangeCount = activeChanges.length;
4167
+ completeOnboardingStep("change");
4000
4168
  lastRecordedSummary = `${target.label} \xB7 ${control.label} ${before}${control.unit ?? ""} \u2192 ${after}${control.unit ?? ""}`;
4001
4169
  updateChangeCount(activeChanges.length, activeChanges.at(-1));
4002
4170
  showToast(ambiguous ? "Choose the source intent in review" : "Change recorded");
@@ -5433,6 +5601,7 @@ function installFoundryInspector(options = {}) {
5433
5601
  selectedElements = [element];
5434
5602
  selected = element;
5435
5603
  }
5604
+ completeOnboardingStep("selection");
5436
5605
  if (!sectionPreferenceTouched) {
5437
5606
  const categoryKeys = [
5438
5607
  "position",
@@ -6033,6 +6202,25 @@ function installFoundryInspector(options = {}) {
6033
6202
  }
6034
6203
  });
6035
6204
  statusPopover.querySelector("[data-status-retry]").addEventListener("click", () => void hydrateSession());
6205
+ statusPopover.querySelector("[data-status-checklist]").addEventListener("click", () => {
6206
+ localStorage.removeItem(onboardingStorageKey("dismissed"));
6207
+ renderOnboardingChecklist(true);
6208
+ statusPopover.hidden = true;
6209
+ sessionStatus.setAttribute("aria-expanded", "false");
6210
+ });
6211
+ statusPopover.querySelector("[data-status-diagnostics]").addEventListener("click", () => {
6212
+ const diagnostics = createSafeDiagnostics({
6213
+ interfaceTheme: resolvedInterfaceTheme(),
6214
+ runtimeConnected,
6215
+ agentConnected: activeAgentPresence.connected,
6216
+ agentName: activeAgentPresence.presence?.agent?.name,
6217
+ selectedCount: selectedElements.length,
6218
+ recordedChangeCount,
6219
+ latestApplyState
6220
+ });
6221
+ void navigator.clipboard.writeText(JSON.stringify(diagnostics, null, 2));
6222
+ showToast("Privacy-safe diagnostics copied");
6223
+ });
6036
6224
  interfaceThemeTrigger.addEventListener("click", () => {
6037
6225
  if (interfaceThemeMenu.hidden) openInterfaceThemeMenu();
6038
6226
  else closeInterfaceThemeMenu();
@@ -6045,15 +6233,36 @@ function installFoundryInspector(options = {}) {
6045
6233
  );
6046
6234
  changeDock.querySelector(".dock-undo")?.addEventListener("click", () => void replayHistory(-1));
6047
6235
  changeDock.querySelector(".dock-review")?.addEventListener("click", () => void openReview());
6236
+ onboarding.querySelector(".onboarding-close")?.addEventListener("click", () => {
6237
+ localStorage.setItem(onboardingStorageKey("dismissed"), "1");
6238
+ onboarding.hidden = true;
6239
+ });
6048
6240
  onboarding.querySelector(".onboarding-start")?.addEventListener("click", () => {
6049
- localStorage.setItem("__foundry_onboarded", "1");
6241
+ const action = onboarding.querySelector(".onboarding-start")?.dataset.action;
6242
+ if (action === "agent") {
6243
+ void navigator.clipboard.writeText(
6244
+ "Start Foundry for this project and keep listening for Apply with agent requests."
6245
+ );
6246
+ showToast("Restart prompt copied");
6247
+ return;
6248
+ }
6249
+ if (action === "apply" && recordedChangeCount > 0) {
6250
+ onboarding.hidden = true;
6251
+ void openReview();
6252
+ return;
6253
+ }
6254
+ if (action === "done") {
6255
+ onboarding.hidden = true;
6256
+ return;
6257
+ }
6050
6258
  onboarding.hidden = true;
6051
6259
  inspecting = true;
6052
6260
  updateInspectionMode();
6053
- showToast("Select mode stays on \xB7 click any element to begin");
6261
+ showToast(
6262
+ action === "change" ? "Adjust any Inspector value to record a change" : "Select mode stays on \xB7 click any element to begin"
6263
+ );
6054
6264
  });
6055
6265
  onboarding.querySelector(".onboarding-shortcuts")?.addEventListener("click", () => {
6056
- localStorage.setItem("__foundry_onboarded", "1");
6057
6266
  onboarding.hidden = true;
6058
6267
  openCommands();
6059
6268
  });
@@ -0,0 +1,27 @@
1
+ export interface SafeDiagnosticsInput {
2
+ interfaceTheme: 'light' | 'dark';
3
+ runtimeConnected: boolean;
4
+ agentConnected: boolean;
5
+ agentName?: string;
6
+ selectedCount: number;
7
+ recordedChangeCount: number;
8
+ latestApplyState?: string;
9
+ protocolVersion?: string;
10
+ }
11
+ /** Build useful support state without exposing project content or session credentials. */
12
+ export declare function createSafeDiagnostics(input: SafeDiagnosticsInput): {
13
+ product: string;
14
+ protocolVersion: string;
15
+ interfaceTheme: "light" | "dark";
16
+ connection: {
17
+ agentName?: string | undefined;
18
+ runtime: string;
19
+ agent: string;
20
+ };
21
+ workspace: {
22
+ selectedElementCount: number;
23
+ recordedChangeCount: number;
24
+ latestApplyState: string;
25
+ };
26
+ };
27
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,oBAAoB;;;;;;;;;;;;;;EAgBhE"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6DA,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,IAAI,IAAI,CAAC;IAChB,cAAc,IAAI,IAAI,CAAC;IACvB,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AA6+BD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,uBAA4B,GACpC,0BAA0B,CAqvK5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA8DA,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,IAAI,IAAI,CAAC;IAChB,cAAc,IAAI,IAAI,CAAC;IACvB,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AAu/BD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,uBAA4B,GACpC,0BAA0B,CAg9K5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foundry-design-web-adapter",
3
- "version": "0.2.0-beta.3",
3
+ "version": "0.2.0-beta.5",
4
4
  "description": "Development-only browser inspector for Foundry",
5
5
  "type": "module",
6
6
  "main": "dist/adapter.js",