Skip to content

[Demo PR] Multi-sprite Tutorial Mode UI Design - #3429

Draft
wenjiangping wants to merge 148 commits into
goplus:uifrom
wenjiangping:issue-3412-B
Draft

wenjiangping wants to merge 148 commits into
goplus:uifrom
wenjiangping:issue-3412-B

Conversation

@wenjiangping

Copy link
Copy Markdown

Related

Related to #3425

Background

The multi-sprite tutorial editor should preserve the standard editor layout used by the dev environment while providing the same ruler interaction available in the single-sprite tutorial mode. The existing course-filtered API list also needs to remain understandable by keeping its visible categories aligned with the code items used by the current course.

UX Changes

  • Keep the regular editor layout and add the ruler button to the preview header without changing the existing preview, run, publish, sprite, or stage workflows.
  • Match the ruler interaction and disabled-running state used by the single-sprite tutorial mode.
  • Reuse the dev environment API category navigation and show only categories and code sections that contain items selected for the current course.
  • Hide empty category groups and keep category navigation linked to the corresponding filtered code sections.
  • Preserve accessible names, keyboard focus behavior, and pressed-state feedback for the ruler and API category controls.

Scope

  • Pages / components: EditorPreview, APIReferenceUI, and the multi-sprite tutorial editor experience.
  • Main user path: enter a multi-sprite tutorial, edit sprite code, browse course-filtered API references, measure stage distances, and run the project.
  • Backend / APIs: no backend logic, data model, or API interface changes.

Verification

  • Compared the editor layout and API category navigation with the dev environment.
  • Checked the local tutorial preview in the multi-sprite course.
  • Verified default, selected, disabled-running, filtered, and empty-category states.
  • Passed ESLint and Prettier checks.
  • Passed the focused regression suite: 39 tests.

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>
Ethanlita and others added 8 commits August 6, 2026 10:25
The label lives inside the stage container, so moving across it bubbled a
mousemove up to the stage's hover tracking, which hit-tested that point,
found no sprite under the label, and started hiding the thing the pointer was
sitting on. Entering it cancelled the countdown once; the next move restarted
it. The label swallows its own mousemove now, and starts the countdown when
the pointer actually leaves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label still vanished now and then once the pointer reached it. Swallowing
its own mousemove was not enough: the stage's hover tracking is throttled, so
a call scheduled while the pointer was still crossing the gap could run after
it had arrived, hit-test that stale point, find no sprite, and start hiding
the label the pointer was resting on — and with the label swallowing events,
nothing was left to cancel the countdown. Whether that trailing call landed
before or after the pointer arrived is what made it intermittent.

The pointer being on a label is now a fact the hide path checks, rather than
something inferred from whichever event happens to arrive last.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – builder a63f3a5b Deployed Aug 21, 2026 by vercel[bot]
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.

3 participants