diff --git a/playground/src/analytics.ts b/playground/src/analytics.ts
index 408751b..b24d6af 100644
--- a/playground/src/analytics.ts
+++ b/playground/src/analytics.ts
@@ -12,6 +12,10 @@ export const USAGE_EVENT_NAMES = {
promptCopied: "prompt_copied",
movementAttempted: "movement_attempted",
shareCreated: "share_created",
+ guidedEditShown: "guided_edit_shown",
+ guidedEditStarted: "guided_edit_started",
+ guidedEditCompleted: "guided_edit_completed",
+ guidedEditDismissed: "guided_edit_dismissed",
embedDocsClicked: "embed_docs_clicked",
installCommandCopied: "install_command_copied",
} as const;
@@ -30,6 +34,8 @@ export type RenderTrigger =
export type ShareKind = "preset" | "encoded";
export type PromptLocation = "landing" | "playground";
export type InstallCommand = "embed" | "packages" | "mcp";
+export type GuidedEditSurface = "desktop" | "mobile";
+export type GuidedEditStage = "edit" | "success";
export interface UsageEventMap {
preset_opened: { source: PresetOpenSource; preset_id: string };
@@ -38,6 +44,16 @@ export interface UsageEventMap {
prompt_copied: { location: PromptLocation };
movement_attempted: Record;
share_created: { share_kind: ShareKind };
+ guided_edit_shown: { experiment: "superhero_first_edit_v1" };
+ guided_edit_started: {
+ experiment: "superhero_first_edit_v1";
+ surface: GuidedEditSurface;
+ };
+ guided_edit_completed: { experiment: "superhero_first_edit_v1" };
+ guided_edit_dismissed: {
+ experiment: "superhero_first_edit_v1";
+ stage: GuidedEditStage;
+ };
embed_docs_clicked: { location: "for_products" };
install_command_copied: {
command: InstallCommand;
diff --git a/playground/src/editor.ts b/playground/src/editor.ts
index 7c120ee..9740f3b 100644
--- a/playground/src/editor.ts
+++ b/playground/src/editor.ts
@@ -601,6 +601,8 @@ export interface PosecodeEditor {
getValue(): string;
setValue(doc: string): void;
focus(): void;
+ /** Reveal and focus a direct angle spinner for the requested joint action. */
+ focusAngleControl(joint: string, action: string): boolean;
/** Highlight an inclusive 1-based line range as the active phase; null clears. */
highlightPhase(from: number | null, to?: number): void;
}
@@ -744,6 +746,23 @@ export function createPosecodeEditor(
opts.onJointSelect?.(null, []);
},
focus: () => view.focus(),
+ focusAngleControl: (joint: string, action: string) => {
+ const target = findAngleTargets(view.state.doc.toString()).find(
+ (candidate) =>
+ candidate.joint === joint && candidate.action === action,
+ );
+ if (!target) return false;
+ view.dispatch({
+ selection: { anchor: target.angleFrom, head: target.angleTo },
+ effects: [
+ setSelectedJoint.of(target.joint),
+ setActiveAngle.of(target),
+ EditorView.scrollIntoView(target.angleFrom, { y: "center" }),
+ ],
+ });
+ opts.onJointSelect?.(target.joint, expandJoint(target.joint));
+ return true;
+ },
highlightPhase: (from: number | null, to?: number) => {
view.dispatch({
effects: setPhaseHighlight.of(
diff --git a/playground/src/guided-first-edit.ts b/playground/src/guided-first-edit.ts
new file mode 100644
index 0000000..47c8640
--- /dev/null
+++ b/playground/src/guided-first-edit.ts
@@ -0,0 +1,120 @@
+import {
+ trackUsageEvent,
+ USAGE_EVENT_NAMES,
+ type GuidedEditSurface,
+} from "./analytics.js";
+import { findAngleTargets } from "./direct-manipulation.js";
+
+export const GUIDED_FIRST_EDIT_EXPERIMENT = "superhero_first_edit_v1";
+
+export const GUIDED_FIRST_EDIT_TARGET = {
+ joint: "knee_right",
+ action: "flex",
+ initialDegrees: 123,
+ suggestedDegrees: 115,
+} as const;
+
+export type GuidedFirstEditStage =
+ | "idle"
+ | "edit"
+ | "success"
+ | "dismissed";
+
+export interface GuidedEditFocusRequest {
+ joint: string;
+ action: string;
+ switchToEditor: boolean;
+}
+
+function targetDegrees(source: string): number | null {
+ const target = findAngleTargets(source).find(
+ ({ joint, action }) =>
+ joint === GUIDED_FIRST_EDIT_TARGET.joint &&
+ action === GUIDED_FIRST_EDIT_TARGET.action,
+ );
+ return target?.degrees ?? null;
+}
+
+/** Keep the experiment on the single intended entry point and known source. */
+export function shouldOfferGuidedFirstEdit(
+ pathname: string,
+ hash: string,
+ source: string,
+): boolean {
+ return (
+ pathname === "/play/superhero-landing" &&
+ hash === "" &&
+ targetDegrees(source) === GUIDED_FIRST_EDIT_TARGET.initialDegrees
+ );
+}
+
+/**
+ * Small state machine for the guided activation experiment. Source is inspected
+ * only in the browser and is never included in analytics properties.
+ */
+export class GuidedFirstEditSession {
+ stage: GuidedFirstEditStage = "idle";
+ private targetWasEdited = false;
+ private started = false;
+
+ offer(): boolean {
+ if (this.stage !== "idle") return false;
+ this.stage = "edit";
+ trackUsageEvent(USAGE_EVENT_NAMES.guidedEditShown, {
+ experiment: GUIDED_FIRST_EDIT_EXPERIMENT,
+ });
+ return true;
+ }
+
+ begin(surface: GuidedEditSurface): GuidedEditFocusRequest | null {
+ if (this.stage !== "edit") return null;
+ if (!this.started) {
+ this.started = true;
+ trackUsageEvent(USAGE_EVENT_NAMES.guidedEditStarted, {
+ experiment: GUIDED_FIRST_EDIT_EXPERIMENT,
+ surface,
+ });
+ }
+ return {
+ joint: GUIDED_FIRST_EDIT_TARGET.joint,
+ action: GUIDED_FIRST_EDIT_TARGET.action,
+ switchToEditor: surface === "mobile",
+ };
+ }
+
+ noteUserEdit(source: string, userInitiated: boolean): boolean {
+ if (this.stage !== "edit" || !userInitiated) return false;
+ const degrees = targetDegrees(source);
+ this.targetWasEdited =
+ degrees !== null && degrees !== GUIDED_FIRST_EDIT_TARGET.initialDegrees;
+ return this.targetWasEdited;
+ }
+
+ /** Call only after the parser has accepted and the viewer has loaded source. */
+ confirmValidCustomRender(source: string): boolean {
+ if (this.stage !== "edit" || !this.targetWasEdited) return false;
+ const degrees = targetDegrees(source);
+ if (
+ degrees === null ||
+ degrees === GUIDED_FIRST_EDIT_TARGET.initialDegrees
+ ) {
+ return false;
+ }
+ this.stage = "success";
+ trackUsageEvent(USAGE_EVENT_NAMES.guidedEditCompleted, {
+ experiment: GUIDED_FIRST_EDIT_EXPERIMENT,
+ });
+ return true;
+ }
+
+ dismiss(): boolean {
+ if (this.stage !== "edit" && this.stage !== "success") return false;
+ const stage = this.stage;
+ this.stage = "dismissed";
+ trackUsageEvent(USAGE_EVENT_NAMES.guidedEditDismissed, {
+ experiment: GUIDED_FIRST_EDIT_EXPERIMENT,
+ stage,
+ });
+ return true;
+ }
+}
diff --git a/playground/src/main.ts b/playground/src/main.ts
index 20b36b4..2189afb 100644
--- a/playground/src/main.ts
+++ b/playground/src/main.ts
@@ -29,6 +29,11 @@ import { ANIMATION_PROGRESS_MESSAGE, PRESETS } from "./presets.js";
import { prioritizeFeaturedMovement } from "./library-order.js";
import { SHOWCASE_CLIPS } from "./clips.js";
import { previewTimeForLine } from "./direct-manipulation.js";
+import {
+ GUIDED_FIRST_EDIT_TARGET,
+ GuidedFirstEditSession,
+ shouldOfferGuidedFirstEdit,
+} from "./guided-first-edit.js";
// During source-only typechecks the playground resolves posecode-render's last
// built declaration bundle. Keep the local extension explicit until the normal
@@ -79,6 +84,12 @@ const downloadBvhBtn = $("download-bvh");
const downloadGltfBtn = $("download-gltf");
const tabEditor = $("tab-editor");
const tabViewer = $("tab-viewer");
+const guidedFirstEditCard = $("guided-first-edit");
+const guidedFirstEditTitle = $("guided-first-edit-title");
+const guidedFirstEditMessage = $("guided-first-edit-message");
+const guidedFirstEditStart = $("guided-first-edit-start");
+const guidedFirstEditShare = $("guided-first-edit-share");
+const guidedFirstEditDismiss = $("guided-first-edit-dismiss");
// Three.js is heavy (~530 kB). Like the landing page, we load the renderer
// *after* the editor shell paints (dynamic import → its own chunk) so first
@@ -100,6 +111,8 @@ let documentRevision = 1;
let pendingRenderTrigger: RenderTrigger = "initial";
let selectedBoneIds: readonly string[] = [];
let pendingPreviewLine: number | null = null;
+let guidedFirstEditSession: GuidedFirstEditSession | null = null;
+let pendingGuidedAngleFocus = false;
/** Keep the source selection and its live 3D joint markers in sync. */
function handleJointSelect(
@@ -313,6 +326,7 @@ function handleEditorChange(
const preset = PRESETS.find((p) => p.source === source);
currentPresetId = preset?.id ?? null;
if (userInitiated) {
+ guidedFirstEditSession?.noteUserEdit(source, true);
usageSession.trackFirstEdit(editedDocumentKind);
initialDocumentWasShared = false;
documentRevision++;
@@ -358,6 +372,9 @@ function recompile(): void {
documentKind() === "custom"
) {
usageSession.trackFirstValidCustomMovement();
+ if (guidedFirstEditSession?.confirmValidCustomRender(source)) {
+ renderGuidedFirstEdit();
+ }
}
usageSession.trackSuccessfulRender(
documentRevision,
@@ -582,6 +599,7 @@ function renderLibraryList(): void {
function loadPreset(id: string): void {
const preset = PRESETS.find((p) => p.id === id);
if (!preset) return;
+ dismissGuidedFirstEdit();
currentPresetId = preset.id;
initialDocumentWasShared = false;
documentRevision++;
@@ -608,6 +626,7 @@ renderLibraryList();
// A human can write directly or paste a draft from another tool without
// overwriting a preset by hand-selecting its text first.
$("new-doc").addEventListener("click", () => {
+ dismissGuidedFirstEdit();
currentPresetId = null;
initialDocumentWasShared = false;
setCurrentPresetLabel(undefined, "New movement");
@@ -628,6 +647,48 @@ tabEditor.addEventListener("click", () => setMobileView("editor"));
tabViewer.addEventListener("click", () => setMobileView("viewer"));
setMobileView("viewer");
+function renderGuidedFirstEdit(): void {
+ const stage = guidedFirstEditSession?.stage ?? "idle";
+ guidedFirstEditCard.hidden = stage === "idle" || stage === "dismissed";
+ if (guidedFirstEditCard.hidden) return;
+ guidedFirstEditCard.dataset.stage = stage;
+ const succeeded = stage === "success";
+ guidedFirstEditTitle.textContent = succeeded
+ ? "Nice — your edit rendered."
+ : "Bend the right knee a little less.";
+ guidedFirstEditMessage.textContent = succeeded
+ ? "The source is valid and the figure updated."
+ : `Change knee_right: flex ${GUIDED_FIRST_EDIT_TARGET.initialDegrees} to ${GUIDED_FIRST_EDIT_TARGET.suggestedDegrees}°.`;
+ guidedFirstEditStart.hidden = succeeded;
+ guidedFirstEditShare.hidden = !succeeded;
+ guidedFirstEditDismiss.setAttribute(
+ "aria-label",
+ succeeded ? "Dismiss edit success" : "Dismiss quick edit",
+ );
+}
+
+function dismissGuidedFirstEdit(): void {
+ if (guidedFirstEditSession?.dismiss()) renderGuidedFirstEdit();
+}
+
+function focusGuidedAngleControl(): void {
+ const session = guidedFirstEditSession;
+ if (!session) return;
+ const surface = matchMedia("(max-width: 860px)").matches
+ ? "mobile"
+ : "desktop";
+ const request = session.begin(surface);
+ if (!request) return;
+ if (request.switchToEditor) setMobileView("editor");
+ pendingGuidedAngleFocus = true;
+ if (editorApi?.focusAngleControl(request.joint, request.action)) {
+ pendingGuidedAngleFocus = false;
+ }
+}
+
+guidedFirstEditStart.addEventListener("click", focusGuidedAngleControl);
+guidedFirstEditDismiss.addEventListener("click", dismissGuidedFirstEdit);
+
// --- Transport ---
playpause.addEventListener("click", () => {
if (viewer) setPlaying(viewer.toggle());
@@ -720,9 +781,9 @@ copyBtn.addEventListener("click", () => copyPrompt(copyBtn));
// --- Share (permalink) ---
// Snapshot the current document into a URL hash, reflect it in the address bar
// (so it's bookmarkable), and copy the full link to the clipboard.
-async function shareLink(): Promise {
+async function shareLink(feedbackButton = shareBtn): Promise {
if (!editorApi) return; // editor still loading; nothing to snapshot yet
- flash(shareBtn, "Copying…", "pending", 0);
+ flash(feedbackButton, "Copying…", "pending", 0);
try {
const source = editorApi.getValue();
const path = buildNicePlayPath(source);
@@ -733,7 +794,7 @@ async function shareLink(): Promise {
usageSession.trackSuccessfulShare(
path === "/play" ? "encoded" : "preset",
);
- flash(shareBtn, "Link copied ✓", "success");
+ flash(feedbackButton, "Link copied ✓", "success");
} catch (err) {
const message =
err instanceof TypeError
@@ -741,10 +802,13 @@ async function shareLink(): Promise {
: err instanceof RangeError
? "Too long to link"
: "Copy failed";
- flash(shareBtn, message, "error");
+ flash(feedbackButton, message, "error");
}
}
-shareBtn.addEventListener("click", shareLink);
+shareBtn.addEventListener("click", () => void shareLink());
+guidedFirstEditShare.addEventListener("click", () =>
+ void shareLink(guidedFirstEditShare),
+);
// --- BVH export ---
// Bake the current movement's authored motion into a .bvh file and hand it to
@@ -954,6 +1018,18 @@ if (sharedSource) {
});
}
+if (
+ shouldOfferGuidedFirstEdit(
+ window.location.pathname,
+ window.location.hash,
+ initialDoc,
+ )
+) {
+ guidedFirstEditSession = new GuidedFirstEditSession();
+ guidedFirstEditSession.offer();
+ renderGuidedFirstEdit();
+}
+
// Boot the two heavyweights (CodeMirror editor + Three.js renderer) after the
// shell paints, each in its own lazy chunk, so neither is on the critical path.
// They load independently; recompile() self-guards until both are ready, and
@@ -966,6 +1042,12 @@ void import("./editor.js").then(({ createPosecodeEditor }) => {
onChange: handleEditorChange,
onJointSelect: handleJointSelect,
});
+ if (pendingGuidedAngleFocus) {
+ pendingGuidedAngleFocus = !editorApi.focusAngleControl(
+ GUIDED_FIRST_EDIT_TARGET.joint,
+ GUIDED_FIRST_EDIT_TARGET.action,
+ );
+ }
recompile();
});
diff --git a/playground/src/style.css b/playground/src/style.css
index 81c0280..0222cd7 100644
--- a/playground/src/style.css
+++ b/playground/src/style.css
@@ -1479,6 +1479,51 @@ select:hover {
box-shadow: 0 4px 14px -6px var(--accent-glow);
}
+/* --- Guided first edit ---------------------------------------------------- */
+.guided-first-edit {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ padding: 10px 20px;
+ color: var(--text-2);
+ background: #121512;
+ border-bottom: 1px solid var(--accent-line);
+}
+.guided-first-edit-copy {
+ min-width: 0;
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ font-size: 12.5px;
+}
+.guided-first-edit-kicker {
+ flex: none;
+ color: var(--accent);
+ font-family: var(--mono);
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: .8px;
+ text-transform: uppercase;
+}
+.guided-first-edit-copy strong {
+ flex: none;
+ color: var(--text);
+}
+.guided-first-edit-copy code {
+ color: var(--text);
+ font-family: var(--mono);
+ font-size: 11.5px;
+}
+.guided-first-edit-actions {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ gap: 9px;
+}
+.guided-first-edit[data-stage="success"] {
+ background: rgba(212, 255, 63, .07);
+}
+
/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 860px) {
body {
@@ -1700,6 +1745,29 @@ select:hover {
display: none;
}
.intro { padding-left: 12px; padding-right: 12px; }
+ .guided-first-edit {
+ align-items: flex-start;
+ gap: 10px;
+ padding: 11px 12px;
+ }
+ .guided-first-edit-copy {
+ flex: 1;
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 2px;
+ line-height: 1.4;
+ }
+ .guided-first-edit-kicker {
+ margin-bottom: 2px;
+ }
+ .guided-first-edit-actions {
+ flex: none;
+ align-items: flex-start;
+ }
+ .guided-first-edit-actions .btn {
+ white-space: normal;
+ text-align: center;
+ }
.layout { grid-template-columns: 1fr; }
.floor-guide-key {
/* Clear the phase plus a two-line coaching cue instead of overlaying it. */
diff --git a/playground/test/analytics.test.ts b/playground/test/analytics.test.ts
index a155d11..7843255 100644
--- a/playground/test/analytics.test.ts
+++ b/playground/test/analytics.test.ts
@@ -23,6 +23,10 @@ describe("product usage analytics", () => {
promptCopied: "prompt_copied",
movementAttempted: "movement_attempted",
shareCreated: "share_created",
+ guidedEditShown: "guided_edit_shown",
+ guidedEditStarted: "guided_edit_started",
+ guidedEditCompleted: "guided_edit_completed",
+ guidedEditDismissed: "guided_edit_dismissed",
embedDocsClicked: "embed_docs_clicked",
installCommandCopied: "install_command_copied",
});
diff --git a/playground/test/guided-first-edit.test.ts b/playground/test/guided-first-edit.test.ts
new file mode 100644
index 0000000..b5f8f19
--- /dev/null
+++ b/playground/test/guided-first-edit.test.ts
@@ -0,0 +1,174 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ configureUsageAnalytics,
+ type UsageEventSink,
+} from "../src/analytics.js";
+import {
+ GUIDED_FIRST_EDIT_EXPERIMENT,
+ GuidedFirstEditSession,
+ shouldOfferGuidedFirstEdit,
+} from "../src/guided-first-edit.js";
+
+const root = resolve(import.meta.dirname, "../..");
+const superhero = readFileSync(
+ resolve(root, "spec/examples/superhero-landing.posecode"),
+ "utf8",
+);
+
+describe("guided first-edit activation", () => {
+ const sink = vi.fn();
+
+ beforeEach(() => {
+ sink.mockClear();
+ configureUsageAnalytics(sink);
+ });
+
+ it("is offered only on the exact default superhero route and known source", () => {
+ expect(
+ shouldOfferGuidedFirstEdit(
+ "/play/superhero-landing",
+ "",
+ superhero,
+ ),
+ ).toBe(true);
+ expect(shouldOfferGuidedFirstEdit("/play", "", superhero)).toBe(false);
+ expect(
+ shouldOfferGuidedFirstEdit(
+ "/play/superhero-landing",
+ "#doc=private",
+ superhero,
+ ),
+ ).toBe(false);
+ expect(
+ shouldOfferGuidedFirstEdit(
+ "/play/superhero-landing",
+ "",
+ superhero.replace("knee_right: flex 123", "knee_right: flex 115"),
+ ),
+ ).toBe(false);
+ });
+
+ it("focuses in place on desktop and requests the Editor panel on mobile", () => {
+ const desktop = new GuidedFirstEditSession();
+ desktop.offer();
+ expect(desktop.begin("desktop")).toEqual({
+ joint: "knee_right",
+ action: "flex",
+ switchToEditor: false,
+ });
+
+ const mobile = new GuidedFirstEditSession();
+ mobile.offer();
+ expect(mobile.begin("mobile")).toEqual({
+ joint: "knee_right",
+ action: "flex",
+ switchToEditor: true,
+ });
+
+ expect(sink.mock.calls).toContainEqual([
+ "guided_edit_started",
+ { experiment: GUIDED_FIRST_EDIT_EXPERIMENT, surface: "desktop" },
+ ]);
+ expect(sink.mock.calls).toContainEqual([
+ "guided_edit_started",
+ { experiment: GUIDED_FIRST_EDIT_EXPERIMENT, surface: "mobile" },
+ ]);
+ });
+
+ it("dismisses from the edit state without allowing later completion", () => {
+ const session = new GuidedFirstEditSession();
+ session.offer();
+ expect(session.dismiss()).toBe(true);
+ expect(session.stage).toBe("dismissed");
+
+ const changed = superhero.replace(
+ "knee_right: flex 123",
+ "knee_right: flex 115",
+ );
+ expect(session.noteUserEdit(changed, true)).toBe(false);
+ expect(session.confirmValidCustomRender(changed)).toBe(false);
+ expect(sink).toHaveBeenCalledWith("guided_edit_dismissed", {
+ experiment: GUIDED_FIRST_EDIT_EXPERIMENT,
+ stage: "edit",
+ });
+ expect(sink).not.toHaveBeenCalledWith(
+ "guided_edit_completed",
+ expect.anything(),
+ );
+ });
+
+ it("completes only after the target user edit receives a valid custom render", () => {
+ const session = new GuidedFirstEditSession();
+ session.offer();
+ expect(session.confirmValidCustomRender(superhero)).toBe(false);
+
+ const unrelated = superhero.replace(
+ "chest: flex 9",
+ "chest: flex 10",
+ );
+ expect(session.noteUserEdit(unrelated, true)).toBe(false);
+ expect(session.confirmValidCustomRender(unrelated)).toBe(false);
+
+ const changed = superhero.replace(
+ "knee_right: flex 123",
+ "knee_right: flex 115",
+ );
+ expect(session.noteUserEdit(changed, false)).toBe(false);
+ expect(session.noteUserEdit(changed, true)).toBe(true);
+ expect(session.confirmValidCustomRender(changed)).toBe(true);
+ expect(session.stage).toBe("success");
+ expect(sink).toHaveBeenCalledWith("guided_edit_completed", {
+ experiment: GUIDED_FIRST_EDIT_EXPERIMENT,
+ });
+
+ const analyticsPayload = JSON.stringify(sink.mock.calls);
+ expect(analyticsPayload).not.toContain("knee_right");
+ expect(analyticsPayload).not.toContain("115");
+ expect(analyticsPayload).not.toContain("posecode posture");
+ });
+});
+
+describe("guided first-edit UI wiring", () => {
+ const html = readFileSync(resolve(root, "playground/play.html"), "utf8");
+ const main = readFileSync(resolve(root, "playground/src/main.ts"), "utf8");
+ const editor = readFileSync(resolve(root, "playground/src/editor.ts"), "utf8");
+
+ it("provides accessible edit, Share, and dismissal controls", () => {
+ expect(html).toContain('id="guided-first-edit"');
+ expect(html).toContain('aria-labelledby="guided-first-edit-title"');
+ expect(html).toContain('id="guided-first-edit-message" aria-live="polite"');
+ expect(html).toContain('id="guided-first-edit-share"');
+ expect(html).toContain('aria-label="Dismiss quick edit"');
+ });
+
+ it("switches mobile before focusing the existing direct angle control", () => {
+ const switchPanel = main.indexOf(
+ 'if (request.switchToEditor) setMobileView("editor")',
+ );
+ const focusControl = main.indexOf(
+ "editorApi?.focusAngleControl(request.joint, request.action)",
+ switchPanel,
+ );
+ expect(switchPanel).toBeGreaterThan(-1);
+ expect(focusControl).toBeGreaterThan(switchPanel);
+ expect(editor).toContain("focusAngleControl(joint: string, action: string)");
+ expect(editor).toContain("setActiveAngle.of(target)");
+ expect(editor).toContain("EditorView.scrollIntoView");
+ });
+
+ it("confirms success only in the existing valid custom render branch", () => {
+ const load = main.indexOf("viewer.load(ir)");
+ const valid = main.indexOf('errors.length === 0', load);
+ const custom = main.indexOf('documentKind() === "custom"', valid);
+ const completed = main.indexOf(
+ "guidedFirstEditSession?.confirmValidCustomRender(source)",
+ custom,
+ );
+ expect(load).toBeGreaterThan(-1);
+ expect(valid).toBeGreaterThan(load);
+ expect(custom).toBeGreaterThan(valid);
+ expect(completed).toBeGreaterThan(custom);
+ });
+});