[Demo only DO NOT MERGE] feat(tutorial): silent-by-default copilot, capability-gated guidance, and the Code: Lita series#3355
Conversation
…eries - Show tutorial-specific copy in the leave-editor confirm dialog when a course is active; keep the original copy otherwise - Make the course success modal's "back to course series" button navigate to the current course's series instead of /tutorials Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a one-shot skip flag on Tutorial so explicit, expected navigations away from the editor do not trigger the leave confirmation: - "Back to course series" in the success modal requests the skip before navigating to the series page - startCourse requests the skip before pushing the course entrypoint, which may point to a non-editor route Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A plain one-shot flag could leak: navigations that don't reach the editor's onBeforeRouteLeave (e.g. editor -> editor route updates when starting the next course, or starting a course from a non-editor page) never consume it, so a later genuine leave would silently skip its confirmation. Expire the skip request after skipLeaveConfirmTimeout instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Request the leave-confirm skip before emit('cancelled') so the time-bound
window isn't shortened by the cancel handler
- Wrap "back to course series" navigation in useMessageHandle to surface
router.push rejections, consistent with handleStartNextCourse
- Use sentence case for the button label: "Back to series courses"
- Align the tutorial.ts comment with the actual button label
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Invert the dependency so the editor no longer needs tutorial knowledge. Add an editor-owned `editorLeaveConfirm` controller exposing a generic message override and a one-shot, time-bound skip. Tutorial drives it the same way it drives copilot (TutorialRoot sets the tutorial-specific copy while a course is active; startCourse and the success modal request the skip), and the editor page's leave guard only reads the controller. The editor stays unaware of tutorials (no components/editor -> tutorials import), and pages/editor no longer imports useTutorial. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Instead of auto-applying patches or simply point to the editor area using a purple arrow, Copilot now guides the user to make the
edit themselves in the editor, so children practice writing code by hand.
A reply can render four guide elements(custom elements), each backed by an in-editor overlay:
- [NEW] code-drag-hint — opens a drop target at a line and highlights the matching
API reference item, for inserting a new statement by drag
- [NEW] code-type-hint — opens a pre-indented blank line with a "Type the code here"
chip and a green reference ghost, for typing a new statement
- [NEW] code-delete-hint — highlights a range in red, for deleting it
- [MODIFIED] code-change-hint — shows the deletion (red) and addition (green) at once, for
replacing existing code (inline or whole-line)
The CodeGuideController keeps a single active guide, navigates to it, and
auto-dismisses it once the suggested edit is done (whitespace-insensitive).
Completion for delete/change is region-local: an invisible Monaco decoration
tracks the target range so it stays correct even when the user edits elsewhere.
Shared logic lives in use-code-guide.ts (re-show on stream settle, reopen on
click, skip-if-already-done, blank-line scaffolding) so all elements behave
consistently.
Changes to be committed:
new file: node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
modified: spx-gui/src/components/copilot/CopilotRound.vue
modified: spx-gui/src/components/copilot/context.ts
modified: spx-gui/src/components/copilot/copilot.ts
modified: spx-gui/src/components/editor/copilot/CodeChange.vue
new file: spx-gui/src/components/editor/copilot/CodeDeleteHint.vue
new file: spx-gui/src/components/editor/copilot/CodeDragHint.vue
new file: spx-gui/src/components/editor/copilot/CodeTypeHint.vue
modified: spx-gui/src/components/editor/copilot/index.ts
new file: spx-gui/src/components/editor/copilot/use-code-guide.ts
modified: spx-gui/src/components/xgo-code-editor/index.ts
modified: spx-gui/src/components/xgo-code-editor/ui/CodeEditorUI.vue
modified: spx-gui/src/components/xgo-code-editor/ui/api-reference/APIReferenceItem.vue
modified: spx-gui/src/components/xgo-code-editor/ui/api-reference/index.ts
modified: spx-gui/src/components/xgo-code-editor/ui/code-editor-ui.ts
new file: spx-gui/src/components/xgo-code-editor/ui/code-guide/CodeGuideUI.vue
new file: spx-gui/src/components/xgo-code-editor/ui/code-guide/index.test.ts
new file: spx-gui/src/components/xgo-code-editor/ui/code-guide/index.ts
modified: spx-gui/src/components/xgo-code-editor/ui/common.ts
…rials Let a tutorial course focus the "API References" panel on just the APIs it uses, so learners are not distracted by the full catalog. - Generic editor: add an `apiReferenceFilter` predicate on CodeEditor; the API reference controller derives the shown items from it synchronously, so the panel updates reactively without re-running the async provider. - Copilot: an `api-reference-filter` custom element narrows the panel to a set of API definition ids, and a `list_api_reference_items` tool lets the copilot look up the exact ids to use. - Tutorial: the element is registered only while a course is active and the filter is reset when the course ends; the course prompt instructs the copilot to narrow the panel to the APIs the whole course uses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Only the success modal's course navigations leave the editor as an explicit, expected action; other startCourse callers (course-start / course-series pages) are not leaving an editor. Request the skip at the success modal call site instead of inside startCourse, so it no longer applies to every caller. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ride Use a single leave-confirmation message for all effect-free cases instead of a tutorial-specific one. The copy also drops the "if you leave now" clause, which could imply the edits are saveable later. editorLeaveConfirm now only exposes the one-shot skip, and the tutorial layer no longer drives the confirmation copy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng, navbar course entry & story video During a tutorial course the editor switches to a focused workspace layout: the game preview takes ~48% of the workspace (min-width 660px), run/rerun/stop float at the bottom-right corner of the preview, and code is rendered with a larger fixed font. Which workspace areas get hidden (resource panels, edit-mode switch, preview header, code editor tools) is decided by the copilot via a new <workspace-hidden-areas> custom element, following the api-reference-filter pattern: the editor exposes a generic workspace-layout extension point and stays ignorant of who drives it. Also: - The navbar now hosts the in-course state entry with the exit-course action (via a new EditorNavbar slot filled by the editor page). - Exiting a course navigates back to the course list page; the course is kept if the navigation is aborted. - A story video modal (configured via VITE_TUTORIAL_STORY_VIDEO_URL) is shown on the course start page before the course begins; course items on the series page now link to the start page instead of starting directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restarting a course reloads the editor's project state (dropping in-memory edits of the effect-free editing session) and starts the copilot session over. The editor exposes this as a generic reload extension point (editor-reload.ts) driven by the tutorial, keeping the editor ignorant of who requests it. The navbar course entry gains a confirm-guarded "Restart course" menu item. The course start page now accepts a `video` query param to specify the story video per link. Since the param is attacker-controlled, only URLs on the current origin or the usercontent host are accepted; anything else falls back to the configured default. A per-course field on the Course API should replace this once the backend supports it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…able code in chat The course start page now runs an opening sequence: the story video, then an optional text guide the course author embeds in the course prompt as a <course-prelude> section — no backend change needed; the copilot is told the prelude was already shown. The course success element accepts a copilot-written `comment` evaluating the user's actual solution, shown in the success dialog together with a new "Try again" action that restarts the course. Completion guidance now treats goal criteria from the course prompt as the source of truth instead of exact code match. Topics gain a generic `hideCodeInChat` flag (set by tutorial courses): code blocks and code-hint elements keep driving their in-editor guides but no longer display copyable code in the chat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The course copilot gains richer, capability-first ways to guide beyond chat text: - Spotlight now supports a mask mode: everything except the revealed element is dimmed with a soft-edged overlay (never blocking pointer events), with the tip bubble sitting in the dimmed area. The new <spotlight-hint> element triggers it proactively on message arrival (gated to the live round). - <guide-modal> shows must-not-miss guidance in a centered modal, reopenable from a chip in the chat. - <api-video> plays the globally reused explainer video of an API from a library keyed by API definition ID; watched APIs are tracked as learned. - <stay-silent> lets the copilot explicitly do nothing: silent rounds are skipped by the chat UI (falling back to the last meaningful round) and are shown only in developer mode for prompt debugging. The course topic now instructs the copilot to stay silent by default on user events, intervening only when the user is stuck, asks, or needs a genuinely undiscoverable instruction — and to prefer in-editor guides, spotlight, videos and modals over text when it does respond. Also surface sign-in failures (e.g. unregistered redirect URI) as an error toast instead of silently doing nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The context message (custom-element definitions + ambient context + topic instructions) is a single API message, and the backend rejects messages longer than `copilotMessageContentMaxLength` — a tutorial course session (long course prompt, many registered elements, full editor UI info and preloaded skill documents) exceeded it, failing every round with "invalid content: text too long". Truncate the ambient context to the remaining budget, keeping the custom-element definitions and topic instructions intact: they drive the copilot's behavior, while the ambient tail (skill documents etc.) is re-loadable through tools. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e & add step explainer video entry The guide modal is for the one thing the user must not miss, so its content is now raw plain text (no markdown, no nested elements), without a title, and the copilot is instructed to keep it within 30 characters. The API video library gains its first entry (Sprite.step), served from public/tutorial-api-videos/ during development. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…at course start Interventions now follow an escalation ladder: a stuck user first gets one short plain-text hint via the guide modal (direction only, never the answer), then a spotlight / API video, and concrete code guides only after those failed or the user asks for the solution. The guide modal is repositioned accordingly: an intervention tool for stuck users, not a course-opening banner. Course authors declare the new knowledge points of a course in its prompt; the copilot shows the matching explainer videos at the course start, and shows none when no knowledge points are declared. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The course start is now explicitly silent setup: the reply to "Course Started" carries only the invisible setup elements plus declared knowledge-point videos — no greeting, no goal restatement, no proactive pointers to UI locations. The speak-up conditions drop the "next step needs an instruction" loophole: interventions happen only on direct questions, repeated failures (one failure is not enough), or sustained deviation. The example dialogue is rewritten accordingly — the old one demonstrated a chatty welcome with step-by-step highlight links, which taught exactly the proactive behavior the protocol forbids; the new one demonstrates silent setup, staying silent through exploration and first failure, a directional hint on repeated failure, a spotlight only when asked, and success with an evaluation comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The opening reply occasionally carried a sentence narrating the setup
("Let me set up the workspace..."); the protocol now states explicitly
that the reply is the setup elements and nothing else.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uto-open the panel Every user event used to pop the copilot panel open, so collapsing it during a tutorial course (where events are frequent) was futile. Closing the panel by the fold button or by dragging it out of bounds now counts as an explicit collapse: events keep feeding the session so the copilot keeps perceiving, but the panel stays closed until the user opens it again or a new session starts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed API videos
The copilot gains a UI-mode extension point ('floating' | 'docked'); the
tutorial docks it during a course. In docked mode a fixed circular trigger
sits at the bottom-right corner with the panel anchored above it (arrow
tail, resizable height via the dragger, persisted); clicking the trigger
or anywhere outside dismisses the panel as an explicit collapse. The
focused-layout run controls move beside the trigger, matching the
game-window design.
API explainer videos no longer render a card in the chat: the element
opens the video modal directly when its (live) message arrives, and the
copilot re-emits it on request. The course protocol now also forbids
re-emitting setup elements on later events — re-emitted videos would pop
over whatever the user is doing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… is on screen During a course the copilot no longer only waits for user input: while it has no visible presence — no guidance modal / API video / in-editor code guide (tracked via a new visible-artifact registry on Copilot) and no visible reply in the open panel — an "Auto perception" event is sent every 5 seconds so it can observe the latest editor state. Perception pauses while the page is hidden, while a round is pending, and after 4 consecutive silent perceptions (the protocol asks for a hint on the 3rd; the cap guards against disobedience); any other user event resets it. The course protocol distinguishes the sources: auto perceptions are not user requests and follow the strict silence rules, while the "Next step" quick input is an explicit ask deserving an immediate (still restrained) response. Auto perception events do not auto-open the panel (notifyUserEvent gains an autoOpen option). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…thing on screen The "visible reply in the open panel" check treated any non-empty round content as visible, but a reply consisting of render-null elements only (the course setup: workspace hiding, API narrowing, api-video...) shows nothing — with the panel open it blocked auto perception forever right after the course start. Strip all known invisible element tags when judging whether a round produced user-visible output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…k API reference items The copilot occasionally re-emits <api-video> despite instructions (e.g. on auto-perception rounds), popping the video over the user repeatedly. Auto-play now happens at most once per API per session; re-emissions render a small chip the user clicks to (re)play, and the element description tells the copilot so. In the focused layout the API reference items render as draggable blocks — card surface with a grip-dots handle and grab cursor — so dragging them into the code editor reads as the obvious interaction. Wired as a pass-through prop (editor → CodeEditorUI → APIReferenceUI), keeping xgo-code-editor ignorant of the workspace layout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The silence protocol over-generalized: a plain greeting typed by the user is not a question, so the copilot classified it as an event needing no reaction and stayed silent — which reads as "the copilot ignores me", and breaks courses whose goal is interacting with the copilot itself. The protocol now distinguishes the two by their wire format: silence applies only to <event>-wrapped messages; anything the user typed always gets a response. Course-specific completion criteria (possibly non-coding, e.g. "send the copilot a message") explicitly take precedence over every generic rule, including silence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… leaked reasoning around it
The model occasionally narrates its inner reasoning before the stay-silent
element ("Let me check the current state... <stay-silent />"); the silence
filter required the element to be the entire content, so the leaked English
monologue was shown to the user. The protocol requires stay-silent to stand
alone — when it appears at all, whatever surrounds it was never meant for
the user, so its mere presence now counts as silence (in both the chat
filter and auto-perception counting).
The course protocol additionally forbids writing reasoning into any reply
and requires user-facing text in the user's language.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… teaches Two companions to the existing authoring/payload/verification docs: one mapping the change surface and the reasoning behind each mechanism, one describing the 28-course series — its pacing rules, what each course teaches, and the engine constraints its levels are built around. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements a comprehensive rebuild of the tutorial copilot system, shifting it to a silent-by-default, event-driven architecture. Key additions include an intervention level state machine (Silent, Nudge, Guide) driven by progress verdicts, gated custom elements and in-editor code guides, a stage ruler tool for measuring distances and angles, and a programmatic course runner dev harness for runtime verification. Feedback on the documentation highlights a few inconsistencies, specifically the mention of the deprecated <tutorial-progress> tag, mismatched intervention level thresholds compared to the code, and a stray code fence in the verification guide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| Tutorial course (`TutorialRoot.vue`): `tutorial-progress`, `tutorial-course-success`, | ||
| `tutorial-course-exit-link`, `tutorial-course-abandon-prediction`, | ||
| `tutorial-course-abandon-dismissal`, `api-reference-filter`, `stay-silent`; and — only at | ||
| intervention level ≥ nudge (`tutorial-guidance.ts`) — `guide-modal`, `spotlight-hint`, `api-video`. |
There was a problem hiding this comment.
The documentation mentions <tutorial-progress> here and in lines 315-318. However, the "审查修复变更记录" section (line 247) states that <tutorial-progress/> has been deprecated and replaced by the <user-progress-*> verdicts. To avoid confusion and keep the documentation consistent with the implementation, the description of <tutorial-progress> should be removed from this document.
| * **Level 1 — silent** (the first 5 events since progress): observe only. No guidance, whatever you | ||
| think the user should do. Let them explore, fail, and retry. | ||
| * **Level 2 — nudge** (after 5 events): the user is stuck. Give ONE short hint via | ||
| <guide-modal>...</guide-modal> — plain text, at most 30 characters, no other elements inside, | ||
| pointing the direction (what to check, where to look), NEVER the answer or the code. If the hint did not help, or | ||
| the problem is finding something on screen, point at the exact UI element with | ||
| <spotlight-hint target-id="..." tip="..." /> (everything else is dimmed), or explain the relevant API with | ||
| <api-video api="..." /> when it has an explainer video. | ||
| * **Level 3 — guide** (after 8 events): the nudges did not work. Now guide the concrete code edit | ||
| with the in-editor guides <code-drag-hint> / <code-type-hint> / <code-change-hint> / <code-delete-hint>. (Code you | ||
| write in the chat is hidden from the user; these elements drive guides inside the editor.) |
There was a problem hiding this comment.
The description of intervention levels seems to use simplified thresholds ("after 5 events", "after 8 events"). This might be inconsistent with the actual implementation logic described in docs/develop/tutorial/whats-new.md and the changelog within this file, which mention neutralThreshold = 6, backThreshold = 3, and counters resetting upon escalation. For the LLM to behave correctly and for clarity to human reviewers, the prompt's explanation should accurately reflect the underlying state machine logic. For example, it's unclear how "after 8 events" for Level 3 is reached based on the provided thresholds.
| even if a trailing `<stay-silent />` hides it from the user that round. | ||
| - To inspect the exact request sent TO the model (not the reply), use the `fetch` hook in | ||
| [llm-payload.md](./llm-payload.md#如何在运行时查看真实-payload). | ||
| ``` |
There was a problem hiding this comment.
Review summary
Reviewed the tutorial-refactor / silent-by-default copilot / capability-gated guidance changes across ~92 files (Vue 3 + TS, spx-gui) using code-quality, performance, security, and documentation-accuracy passes. The implementation is generally solid: timers/listeners are cleaned up, guidance levels are enforced as a hard registration boundary (tutorial-guidance.ts), and copilot markdown is sanitized. whats-new.md, course-authoring.md, and verifying-courses.md match the code.
The main issues are documentation drift in llm-payload.md (its main-body "map" and verbatim appendix still describe the pre-refactor model and contradict the shipped code and the sibling whats-new.md), plus one confirmed intervention-state bug on in-course restart. Inline comments cover concrete diff-line findings; broader items are below.
Findings
1. [Bug] Intervention level & counters are not reset on in-course restart — spx-gui/src/components/tutorials/tutorial.ts
restartCurrentCourse() → restartCourse() → startCourse() reassigns this.course.value to the same Course object reference. The TutorialRoot.vue watch(() => tutorial.currentCourse, ...) only fires on a reference change, so on an in-course restart the TutorialIntervention instance is not recreated or reset(). Its levelRef, neutral/back counters, and processedRoundCount all carry over, which (a) contradicts the documented "a course (re)start also resets it", and (b) leaves processedRoundCount larger than the restarted session's rounds.length, so the first verdicts of the restarted course are silently skipped. Note the success-modal "Try again" path is fine — it goes through endCurrentCourse() (sets course.value = null) first, so the null→non-null transition fires the watcher. Only the still-running restartCurrentCourse path is affected. Consider resetting the intervention explicitly on restart. (See inline on tutorial.ts.)
2. [Docs, medium] llm-payload.md intervention thresholds are stale — docs/develop/tutorial/llm-payload.md
The doc states thresholds "nudge=5、guide=8 已按当前代码渲染", but the code uses two verdict counters: neutralThreshold = 6 / backThreshold = 3 (tutorial-intervention.ts:24,26), with no single event count. whats-new.md:34 has the correct values. The verbatim appendix ("三、课程协议全文") likewise reproduces the old event-count ladder and the deprecated <tutorial-progress /> reporting element; it should be regenerated against the current generateTopic in tutorial.ts.
3. [Perf, low] Duplicated per-keystroke full-project serialization — spx-gui/src/components/editor/copilot/user-events.ts:122 and :168
watchCodeChange and watchDiagnostics share the getter () => [stage.code, ...sprites.map(s => s.code)].join('\n'). It re-runs and allocates a full joined copy of all project source on every keystroke — twice (once per watcher) — since the debounce only guards the callbacks, not the getter. The getters are pre-existing, but this PR adds the second watcher over the same shape. Consider one shared computed/version signal both watchers depend on.
4. [Perf, low] Per-keystroke view-zone rebuild for the drag hint — spx-gui/src/components/xgo-code-editor/ui/code-guide/CodeGuideUI.vue:234
dragNewLineZone reads docVersion.value, so the useViewZone effect tears down and rebuilds the (static) drag node on every keystroke while a drag guide is active. Gate on the isNewLineInsertion(...) boolean instead of raw docVersion. Low impact (drag guides are short-lived); the type/change zones are unaffected.
Lower-confidence / worth a look
5. [Robustness] Un-gated onMounted side effects on session restore — spx-gui/src/components/tutorials/tutorial-course-abandon.ts:23,52 (and TutorialCourseSuccess.vue:46)
Unlike the sibling tutorial elements (spotlight-hint.ts, GuideModal.vue, ApiVideo.vue, use-code-guide.ts), the abandon-prediction/dismissal (and success) elements call predictAbandon() / dismissAbandon() / endCurrentCourse() unconditionally in onMounted, without the !(round.isLive && round.isLastRound()) guard. Since copilot sessions are persisted and restored, historical instances re-mount and re-fire on reload, which could spuriously escalate/end a course. These predate the PR but are now relied on within the restore-aware architecture; adopting the same gate would make the element family consistent.
6. [Minor, dev-only hardening] spx-gui/src/apps/xbuilder/pages/devtools/course-runner.vue:190
The ?autorun= handler runs new Function(src)(); it is DEV-gated in router.ts and constrained to startsWith('/'). The prefix check does not reject protocol-relative URLs (//host/...); new URL(autorun, location.origin).origin === location.origin would be a stronger same-origin guard. Not exploitable in production (route absent from prod builds).
Note: the diff patch surfaced a node_modules/.vite/vitest/.../results.json new-file hunk, but it is not in the PR's changed-files list (GET /pulls/3355/files) nor in origin/dev...HEAD, so it is a local diff-generation artifact, not part of this PR — no action needed.
| } | ||
|
|
||
| /** Restart the current course from its initial state. */ | ||
| async restartCurrentCourse(): Promise<void> { |
There was a problem hiding this comment.
Intervention state is not reset on in-course restart. restartCurrentCourse → restartCourse → startCourse sets this.course.value = course to the same Course object reference. The TutorialRoot.vue watch(() => tutorial.currentCourse, ...) fires only on a reference change, so the TutorialIntervention is never recreated/reset() here — its level, neutral/back counters, and processedRoundCount carry over from before the restart. This contradicts the documented "a course (re)start also resets it", and because processedRoundCount is now > the restarted session's rounds.length, the restarted course's first verdicts are silently skipped. (The success-modal "Try again" path is fine — it goes through endCurrentCourse() first, so the null→non-null transition fires the watcher.) Consider resetting the intervention explicitly on restart.
| Editor (`editor/copilot/index.ts`, when editing): `code-link`, and — only while the intervention | ||
| level allows (`editorCopilotCodeGuides` gate) — `code-change-hint`, `code-drag-hint`, | ||
| `code-type-hint`, `code-delete-hint`. | ||
| Tutorial course (`TutorialRoot.vue`): `tutorial-progress`, `tutorial-course-success`, |
There was a problem hiding this comment.
tutorial-progress no longer exists in the code — there are zero matches under spx-gui/src/components/tutorials/; it was replaced by user-progress-ahead / -neutral / -back (user-progress.ts). This registered-elements list is stale (the doc's own changelog at line 247 notes the deprecation). The appendix entry at line 315 and the protocol text at 545/570 reference it too.
| Tutorial course (`TutorialRoot.vue`): `tutorial-progress`, `tutorial-course-success`, | ||
| `tutorial-course-exit-link`, `tutorial-course-abandon-prediction`, | ||
| `tutorial-course-abandon-dismissal`, `api-reference-filter`, `stay-silent`; and — only at | ||
| intervention level ≥ nudge (`tutorial-guidance.ts`) — `guide-modal`, `spotlight-hint`, `api-video`. |
There was a problem hiding this comment.
Incorrect: api-video is registered at Silent (level 1), not "≥ nudge". See tutorial-guidance.ts:45 — [InterventionLevel.Silent]: { elements: [apiVideo], ... }; only guide-modal and spotlight-hint are Nudge-level. whats-new.md:28 documents this correctly ("Silent | api-video only"), so the two docs currently contradict each other. (The protocol only restricts the proactive use of api-video to nudge+, but it is registered — and available for answering — at Silent.)
|
|
||
| #### 2a. Registered custom elements (the emittable tags) | ||
|
|
||
| Global (always, `CopilotRoot.vue`): `page-link`, `highlight-link`. |
There was a problem hiding this comment.
The global registered-elements list omits thinking. CopilotRoot.vue:201-205 registers thinking globally alongside page-link/highlight-link. Relatedly, the appendix at line 357 claims thinking is "代码里现成但没有任何地方注册" (present but never registered) — that is now false and contradicts the doc's own changelog at line 239.
课程内容本身是中文的,写课程的人也是中文读者;这几份文档服务的正是他们。 llm-payload 此前同时保留了等价的中英两版,现在只留中文——附录是发给模型的 逐字原文,保持英文不动。 顺带修正两处:verifying-courses 里一个多余的代码块围栏破坏了后半篇结构; 以及「重复调用 run() 没问题」的说法——事实与之相反,一个页面会话里只有第一次 运行可信,这一条已按实测重写。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This is the document most likely to be read by a reviewer who does not read Chinese — it describes the mechanisms, not the course content — so it follows the repo's `X.md` / `X.zh.md` convention with English as the base name. The other tutorial docs serve course authors and stay in Chinese only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
重构教程内容,更新课程结构和干预层级设计,增加感知事件和课程验证工具。
Mirrors 4fd806d: tighter framing (a textbook, not a script; a Playground), the Guide level named down to its individual code guides, de-escalation stated as "ahead while the counters are already at 0", and the perception section carrying the "perceive actively, intervene passively" principle along with the removal of the Next step button. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three UI clean-ups for tutorial focused mode, none touching the regular editor: - The in-place value-edit helper (grey chip + pencil) is off in block style — the code is simplified and given, so editing a value in place is not offered. Rides the existing apiReferenceBlockStyle signal via the code-editor UI context. - The Format button is hidden along with the other code-editor tools (it follows the code-editor-tools hidden area a course already declares). - The selected sprite shows its name just below its transform box, so a beginner can tell which sprite they are working on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In tutorial block style the API-reference hover card carries no interactive controls, so it is trimmed to what a learner needs: the signature is dropped (it already shows on the list item), the Explain action is dropped, and the explainer video is framed 4:3. With nothing left to interact with, the card no longer needs to stay open while the pointer moves onto it — a new `keepOnContentHover` prop on UIDropdown (default true) lets it close as soon as the pointer leaves the item. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntrol Two focused-mode changes matching the design, neither touching the regular editor: - Columns follow the design's 1:2.5:3 ratio (API reference : code editor : game window). ProjectEditor splits editor:preview 3.5:3, and in block style the code editor's API sidebar becomes a flexible 1 against the Monaco editor's 2.5 (the fixed-width sidebar and its resize handle are only for the regular editor). - One run/stop control, no rerun: a run is either stopped or ends on its own, and either way returns to edit mode (handleExit resets to the initial state in focused mode) — so the corner shows exactly Run in edit mode or Stop while running, as a labelled capsule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The course entry in the navbar opens a control center instead of a small menu: the whole series is listed (each course with its thumbnail), the one in progress is marked and offers a restart, and the others navigate straight to their opening. Exit course sits at the top and Return to tutorial homepage at the bottom. The footer also surfaces the guidance state for debugging — the level in the user's terms plus how far the neutral/back counters are toward the next escalation; the intervention exposes those counters as reactive getters for it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Splits course completion from the copilot's evaluation so the success
dialog no longer waits an LLM round to appear.
- course-config gains `judge` ("code" default / "copilot"), plus `apis`
and `videos` for the jsonc-driven setup to come.
- A `code`-judged course completes when the project prints the completion
sentinel; a new editor-owned runtime-output bridge carries runtime lines
up to the App-level tutorial (which sits above editorCtx), and the
tutorial marks completion instantly on the sentinel.
- The success dialog renders from the tutorial's completion state and
shows at once. The comment area holds a typing placeholder until the
copilot's evaluation arrives (or an 8s timeout falls back to a canned
line); the buttons appear only once it is ready, so the user reads the
evaluation before choosing. The comment sits where the plain "course
completed" line used to.
- The tutorial-course-success element becomes the copilot-judge completion
signal (for courses that run no game), carrying its comment with it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follows the split between judgment and evaluation. The copilot protocol now names both completion paths and keeps the copilot out of the one the frontend owns: - A goal the running game can check (e.g. "collect all the carrots") is judged and signalled by the project itself; the copilot must not declare it. A goal with no game to check it (e.g. "send the copilot a message") stays the copilot's to judge, via <tutorial-course-success>. - On the "Course completed" event — sent when the game-judged path fires — the copilot breaks its usual event silence and replies with one plain evaluation sentence to fill the dialog's comment, without re-announcing success or staying silent. The comment extraction strips any stray elements (a verdict, a stay-silent) so only prose reaches the dialog, whatever the copilot emits alongside. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The focused-mode sprite-name label was placed a fixed distance below the sprite center — a guess that ignored the sprite's on-screen size and the canvas's offset inside its container, so it drifted far from the box and, in taller layouts, floated well above the sprite. Drive it from the transform box instead. The label sits at the box's bottom-center, clearing the anchors and the rotate handle wherever they land, and is refreshed whenever the sprite moves or scales, the map scrolls, or the stage resizes. The box comes from the transformer's on-screen rect (canvas pixels); since the label is positioned within `.stage-viewer` and the canvas can sit offset inside it, that offset is added back — otherwise the label drifts by exactly that gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
API reference blocks hug their signature and the list scrolls horizontally instead of truncating in the narrow column. The ruler toggle is a white card whose face turns turquoise on hover and while measuring, carrying the new tilted-ruler glyph. The docked copilot trigger is the brand gradient square with a white mark that spins while the copilot works, even when the panel is hidden. Run/Stop is a white card framing a solid pill (teal Run / red Stop); it and the copilot trigger anchor to the code column's right edge instead of the bottom-right corner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The control center reads as the designed panel: grey-900 title, inset divider, 44px course rows, an "in progress" mark with a small grey restart tag on the current row, and a bordered white footer button. The ruler toggle sits at the stage's top-left with a 16px inset, its tooltip to the right. The docked copilot trigger is a 50px white card framing the 40px gradient mark — the same framed language as Run — with a 16px gap between the two. Both now sit below the modal tier (z 1000 vs the backdrop's 1100) so dialogs cover them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tutorial entry in the navbar reads as the designed pill — a turquoise capsule around the icon on an otherwise plain button — and the button surface turns grey while the control center is open, for which the dropdown-with-tooltip now exposes its visibility to the trigger slot. The control center's return button goes back to the series page the learner came from, falling back to the tutorials index only when no series is active. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The copilot now has two thin presentation shells around one shared conversation body (CopilotChat): the floating panel stays app-global in CopilotUI, while the docked presentation for the focused tutorial layout is an editor-owned component (EditorCopilot) living in the code column's control row. Both shells inject the same copilot instance, so the conversation carries across seamlessly. With the docked shell in the editor's DOM, its placement is plain layout: ProjectEditor renders the control row at the code column's bottom-right corner, the panel anchors right above the trigger, and the preview teleports its Run/Stop control into the row it can't reach by nesting — no more measuring the column boundary into a viewport-space variable from the outside. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The API-panel narrowing and the knowledge-point videos were carried by the copilot's reply to "Course Started" — a tool round plus a generation, so the panel narrowed and the videos popped many seconds into the course, landing over whatever the user was doing by then. Both are author-declared data (the course config's `apis` / `videos`), so the frontend now applies them the moment the course starts: the panel narrows instantly (entries match a bare name, a dotted name, or a definition ID, covering all overloads) and the videos play right away, queued one by one, skipping already-learned APIs. For such courses the copilot's opening turns purely silent — no setup elements, no tool round — which also makes its first reply fast. Courses whose config declares neither field keep the copilot-driven setup, so existing courses behave unchanged until they are migrated. The video dialog is extracted into ApiVideoModal, shared between the course opening and the copilot's api-video element. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A code-judged course can now declare its completion signal in the
course config — `complete: { log, count }` — completing when `count`
distinct runtime-output lines containing `log` appear within a single
run (the output bridge now signals run starts so the counting resets
per run). This covers collect-N goals whose projects log each pickup,
with no per-project sentinel code; courses declaring nothing keep the
sentinel default.
The knowledge-point video library gains entries for the language
constructs (if / if-else / var / for-in) so their dialog titles read
naturally, and the course-authoring guide documents the full config
schema, the entry-matching rules, judge selection, and the authoring
workflow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuilds the tutorial experience around a different premise: a course is a playground the copilot
watches over, not a script it reads out. Based on the prototype in #3330.
Two new documents describe this in full — what this branch adds
(the change surface and the reasoning behind each mechanism) and
the Code: Lita series (what the 28 courses teach).
The core idea
The copilot is silent by default. It perceives everything through real editor events but says
nothing until the user is stuck, has drifted, or asks. How much it may do is not a matter of prompt
etiquette: an intervention level decides which guidance tools are even registered, so a tool
it must not use at this level does not exist for it.
api-videoonly+ guide-modal,spotlight-hint+ in-editor code guidesThe level moves on the model's own per-round verdict (
<user-progress-ahead/>/neutral/back),counted by the system. A typed question temporarily raises it — someone who asks deserves an answer.
Prompt rules alone proved unreliable with the backend model. The order that works is mechanism
first (unregister the tool), per-round reminder second, protocol prose last.
What's new
Waking on runtime output (not just errors) is what makes coding courses completable at all: the
win condition is a log line.
```jsoncblock in the prompt declares hidden panels andwhether the copilot starts open. Author intent, applied once, not a runtime copilot decision.
sprite also reads the signed turn angle, which is exactly the number
turnexpects. Threecourses are built on it.
<stay-silent/>hides the whole round; only typed rounds appear inchat.
/devtools/course-runner, dev-only) — drives the real WASM runtimeprogrammatically, so a course can be verified without clicking through the editor.
Editor extension points
Features drive the editor through owned extension points; the editor never imports tutorial code:
workspace-layout(focused mode, hideable areas, optional tools),leave-confirm,editor-reload,API-reference filtering, and the code-guides gate.
Testing
193 unit tests pass, 13 test files added — intervention levels and verdict counting, guidance
registration, course config parsing, content visibility, ruler math, workspace layout reset, code
guides, and the course-start flow.
Beyond that, all 28 courses of the Code: Lita series had their reference answers executed against the
real runtime via the harness, checking that every radish in each level is collected: 27 of 27
courses with code pass (course 1 has no code). That exercise surfaced three engine constraints now
documented and designed around — the Main-execution timeout on bare top-level code, inline array
literals failing to compile, and
onTouchStartonly firing on the transition into contact.Not included
The ~25MB of demo/story video assets under
spx-gui/public/tutorial-*/are intentionally left out ofgit; the
stepexplainer already points at a hosted URL and the rest should follow. Course contentitself lives in the backend, imported from a course-series file.
🤖 Generated with Claude Code