Skip to content

[Demo only DO NOT MERGE] feat(tutorial): silent-by-default copilot, capability-gated guidance, and the Code: Lita series#3355

Open
Ethanlita wants to merge 79 commits into
goplus:devfrom
Ethanlita:tutorial-refractor
Open

[Demo only DO NOT MERGE] feat(tutorial): silent-by-default copilot, capability-gated guidance, and the Code: Lita series#3355
Ethanlita wants to merge 79 commits into
goplus:devfrom
Ethanlita:tutorial-refractor

Conversation

@Ethanlita

Copy link
Copy Markdown
Collaborator

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.

Level What exists
Silent api-video only
Nudge + guide-modal, spotlight-hint
Guide + in-editor code guides

The 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

  • Event-driven perception — run/exit/output/code-change/diagnostics/selection, no polling timer.
    Waking on runtime output (not just errors) is what makes coding courses completable at all: the
    win condition is a log line.
  • Course-authored workspace setup — a ```jsonc block in the prompt declares hidden panels and
    whether the copilot starts open. Author intent, applied once, not a runtime copilot decision.
  • The ruler — a course-only stage tool. Drag to measure; endpoints snap to sprites; starting on a
    sprite also reads the signed turn angle, which is exactly the number turn expects. Three
    courses are built on it.
  • Silence that stays silent<stay-silent/> hides the whole round; only typed rounds appear in
    chat.
  • Opening sequence — story video → new-API video (never repeated per user) → one-line prelude.
  • Dev harness (/devtools/course-runner, dev-only) — drives the real WASM runtime
    programmatically, 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 onTouchStart only firing on the transition into contact.

Not included

The ~25MB of demo/story video assets under spx-gui/public/tutorial-*/ are intentionally left out of
git; the step explainer already points at a hosted URL and the rest should follow. Course content
itself lives in the backend, imported from a course-series file.

🤖 Generated with Claude Code

Ethanlita and others added 30 commits June 23, 2026 18:17
…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>
@Ethanlita Ethanlita changed the title feat(tutorial): silent-by-default copilot, capability-gated guidance, and the Code: Lita series [Demo only DO NOT MERGE] feat(tutorial): silent-by-default copilot, capability-gated guidance, and the Code: Lita series Jul 20, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/develop/tutorial/llm-payload.md Outdated
Comment on lines +53 to +56
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`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +556 to +566
* **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.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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).
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line contains a stray code fence that doesn't seem to be closing any code block and might be a typo. It should probably be removed.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 restartspx-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 staledocs/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 serializationspx-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 hintspx-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 restorespx-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> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intervention state is not reset on in-course restart. restartCurrentCourserestartCoursestartCourse 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.

Comment thread docs/develop/tutorial/llm-payload.md Outdated
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`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/develop/tutorial/llm-payload.md Outdated
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`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

Comment thread docs/develop/tutorial/llm-payload.md Outdated

#### 2a. Registered custom elements (the emittable tags)

Global (always, `CopilotRoot.vue`): `page-link`, `highlight-link`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Ethanlita and others added 13 commits July 22, 2026 17:26
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants