devcoach 2.4.0 → 2.5.0-next.580.g7777f02
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/assets/SKILL.md +37 -17
- package/assets/references/calibration.md +1 -1
- package/assets/references/course.md +188 -0
- package/assets/static/course-frame.js +35 -0
- package/assets/static/course-viewer.js +56 -0
- package/dist/{app-ASC7VJ5O.js → app-7MJG2QS2.js} +221 -141
- package/dist/bin.js +1 -1
- package/dist/chunk-3NJXATLN.js +2 -0
- package/dist/chunk-K2UXXRXB.js +15 -0
- package/dist/chunk-W26MDJJQ.js +104 -0
- package/dist/commands-U5CM6KLQ.js +36 -0
- package/dist/{hooks-IPWPXRUV.js → hooks-WV2I4LRA.js} +1 -1
- package/dist/server-HLOAVOPN.js +9 -0
- package/package.json +1 -1
- package/dist/chunk-CTBMPKAV.js +0 -75
- package/dist/chunk-FIIE7I2N.js +0 -2
- package/dist/chunk-IDWQB32Q.js +0 -15
- package/dist/commands-AC2GWDIO.js +0 -36
- package/dist/server-OMPD42ZP.js +0 -9
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ The simplest way to add devcoach to **Claude Code** — one click, nothing to co
|
|
|
108
108
|
/plugin install devcoach@ultimaphoenix
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
The MCP server, hooks, and skill activate on install — no restart needed. Open the [web dashboard](#web-dashboard) right from Claude Code with `/devcoach:ui` (or just ask — the `open_ui` tool); `/devcoach:share` and `/devcoach:import` hand lessons around. Update later with `/plugin marketplace update ultimaphoenix`.
|
|
111
|
+
The MCP server, hooks, and skill activate on install — no restart needed. Open the [web dashboard](#web-dashboard) right from Claude Code with `/devcoach:ui` (or just ask — the `open_ui` tool); `/devcoach:share` and `/devcoach:import` hand lessons around; `/devcoach:course` turns a lesson you couldn't follow into a step-by-step course. Update later with `/plugin marketplace update ultimaphoenix`.
|
|
112
112
|
|
|
113
113
|
The plugin **does not** add the `devcoach` CLI, but the CLI stays a handy companion when you want the dashboard or your data without going through Claude — `devcoach ui`, `stats`, `lessons`, `share` / `import`, `backup`: run them with `npx -y devcoach …`, or install Homebrew / `npm i -g devcoach` for a bare `devcoach`. Just never run `devcoach install` next to the plugin — it owns the hooks.
|
|
114
114
|
|
|
@@ -358,10 +358,10 @@ an AbortController shared across the requests gives you all-or-nothing with
|
|
|
358
358
|
prompt cancellation — the structured-concurrency middle ground.
|
|
359
359
|
### ──────── typescript · mid ────────
|
|
360
360
|
|
|
361
|
-
Did that land? ✅
|
|
361
|
+
Did that land? ✅ knew it (y) · 💡 understood (u) · ❌ couldn't follow (n)
|
|
362
362
|
```
|
|
363
363
|
|
|
364
|
-
Reply `y` or `n` (
|
|
364
|
+
Reply `y` (already knew it — confidence goes up), `u` (new and clear) or `n` (too dense — kept as a seed for a step-by-step course) to shape future lessons — or just move on: no reply means no change.
|
|
365
365
|
|
|
366
366
|
---
|
|
367
367
|
|
|
@@ -428,7 +428,7 @@ The CLI is a secondary interface for querying and managing your coaching data. E
|
|
|
428
428
|
| `devcoach lessons` | Browse lesson history with filters |
|
|
429
429
|
| `devcoach lesson <id>` | Show a single lesson in full |
|
|
430
430
|
| `devcoach star <id>` | Mark a lesson as starred |
|
|
431
|
-
| `devcoach feedback <id> <know\|dont_know\|clear>` | Record
|
|
431
|
+
| `devcoach feedback <id> <know\|understood\|dont_know\|clear>` | Record your answer under a lesson |
|
|
432
432
|
| `devcoach share [id] [--last] [--link] [--file]` | Hand a lesson to a teammate (copyable text, link or `.devcoach.md`) |
|
|
433
433
|
| `devcoach import [source]` | Add a shared lesson — code, link, URL or file; no argument reads the clipboard |
|
|
434
434
|
| `devcoach set max_per_day <n>` | Max lessons per day (default 2) |
|
package/assets/SKILL.md
CHANGED
|
@@ -6,14 +6,16 @@ description: >
|
|
|
6
6
|
up, redoing onboarding, resetting topics, or reviewing/rebuilding their profile
|
|
7
7
|
or notebook; (2) the user asks about their own learning or skill level — what
|
|
8
8
|
they learned recently, their coaching log or lesson history (by period, starred,
|
|
9
|
-
or marked
|
|
9
|
+
or marked couldn't-follow), how good they are at a technology, lessons to revisit,
|
|
10
10
|
or whether new tech from their recent work should be tracked; (3) a devcoach
|
|
11
11
|
hook cue requests a lesson; (4) you just completed substantial technical work
|
|
12
12
|
(code, review, commit, debugging, config, queries, infra) — then evaluate
|
|
13
13
|
silently whether ONE lesson is due, even when coaching is never mentioned;
|
|
14
14
|
(5) the user wants to share a lesson with someone, or hands you a lesson they
|
|
15
15
|
were given — a `devcoach:lesson:` code, a share link, a URL, or a
|
|
16
|
-
`.devcoach.md` file — to import
|
|
16
|
+
`.devcoach.md` file — to import; (6) the user wants to be taught something
|
|
17
|
+
properly — "explain this step by step", "start a course", "teach me X from the
|
|
18
|
+
basics", `/devcoach:course` — or asks about their courses.
|
|
17
19
|
Do NOT use for ordinary development tasks on code, apps, or documents that
|
|
18
20
|
merely contain words like profile, lessons, or notebook — only when the subject
|
|
19
21
|
is the user's own coaching data.
|
|
@@ -56,6 +58,15 @@ projects", "any new tech I should track?" — read `references/review.md` in thi
|
|
|
56
58
|
skill's directory and follow it. Those flows are incremental and non-destructive;
|
|
57
59
|
only an explicit "redo onboarding" goes through `references/onboarding.md`.
|
|
58
60
|
|
|
61
|
+
## Courses
|
|
62
|
+
|
|
63
|
+
When the user wants to be taught something properly — "explain this step by step", "start
|
|
64
|
+
a course on X", "teach me from the basics", "continue the course", `/devcoach:course` — or
|
|
65
|
+
says *yes* to the one offer you make after a ❌ *couldn't follow* — read
|
|
66
|
+
`references/course.md` in this skill's directory and follow it. A course is user-initiated,
|
|
67
|
+
never starts inside a cued turn, explores what the user already knows one question at a
|
|
68
|
+
time, and lives in ONE HTML document you write yourself.
|
|
69
|
+
|
|
59
70
|
## Before delivering a lesson
|
|
60
71
|
|
|
61
72
|
Call the **`get_briefing`** tool — ONE silent call returns everything below (never fetch
|
|
@@ -154,20 +165,27 @@ turn**: both bands, the title line, the same prose you passed as `body`, the tip
|
|
|
154
165
|
final message is the only place the user ever sees the lesson.
|
|
155
166
|
|
|
156
167
|
`log_lesson` never asks the user anything — it only saves. Feedback is collected as
|
|
157
|
-
text under the card: append the prompt "Did that land? ✅
|
|
158
|
-
(n)" DIRECTLY BENEATH the card's closing band — it is the only
|
|
159
|
-
the card, and it may never appear without the card right above it.
|
|
160
|
-
next message loosely: `y` / `yes` /
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
text under the card: append the prompt "Did that land? ✅ knew it (y) · 💡 understood
|
|
169
|
+
(u) · ❌ couldn't follow (n)" DIRECTLY BENEATH the card's closing band — it is the only
|
|
170
|
+
line allowed after the card, and it may never appear without the card right above it.
|
|
171
|
+
Read the user's next message loosely: `y` / `yes` / "knew it" / ✅ → `know` ·
|
|
172
|
+
`u` / "understood" / "got it" / 💡 → `understood` · `n` / `no` / "lost me" / ❌ →
|
|
173
|
+
`dont_know` · anything else — including no reply at all, the user just moving on —
|
|
174
|
+
→ no feedback, drop the question silently. The three answers mean: **know** = already
|
|
175
|
+
knew it (the only one that moves confidence, +1) · **understood** = new, and now
|
|
176
|
+
clear — the level was right · **dont_know** = couldn't follow this session — no
|
|
177
|
+
confidence change; the lesson is kept as a seed for a step-by-step course. Call
|
|
178
|
+
`submit_feedback(id, value)` for `understood` and `dont_know` always, and for `know`
|
|
179
|
+
only when confidence is below the lesson's band (within/above band → already
|
|
180
|
+
calibrated, skip the call). Never call `update_knowledge` on top of feedback.
|
|
181
|
+
|
|
182
|
+
**After a ❌:** offer ONCE, in one line — *"Want a step-by-step course on this? 🎓"* — and
|
|
183
|
+
on a *yes* start it on that message (`references/course.md`). No other follow-up.
|
|
184
|
+
|
|
185
|
+
**Starring:** after feedback, if `get_lessons({search: topic_id})` shows 2+ lessons on
|
|
186
|
+
the topic, offer *"Want to save this one? ⭐"* — `star_lesson` only if the user agrees,
|
|
187
|
+
never silently. A star is also the one moment to offer sharing (see
|
|
188
|
+
`references/sharing.md`).
|
|
171
189
|
|
|
172
190
|
**Sharing:** user-initiated — the user asks to share a lesson, hands you one to import,
|
|
173
191
|
or asks to see one they were given; the only unprompted offer is the one after a star.
|
|
@@ -190,7 +208,9 @@ checkpoints, never touch the notebook.
|
|
|
190
208
|
- "Show me lessons about X" → `get_lessons({category: X})` or `({search: keyword})`
|
|
191
209
|
- "How good am I at X?" / "Show my profile" → `get_profile`
|
|
192
210
|
- "Coaching log" → `get_lessons({period: "all"})`
|
|
193
|
-
- "Lessons to revisit" → `get_lessons({feedback: "dont_know"})`
|
|
211
|
+
- "Lessons to revisit" / "the ones I couldn't follow" → `get_lessons({feedback: "dont_know"})`
|
|
212
|
+
- "My courses" → `get_courses` · "Continue the course" → `get_courses({status: "active"})`
|
|
213
|
+
then `references/course.md` §5 · "Start a course on X" → `references/course.md`
|
|
194
214
|
- "Share this / the last lesson" / "share the lesson about X" → `get_lessons` then
|
|
195
215
|
`share_lesson({lesson_id})` (see Sharing)
|
|
196
216
|
- "Import this lesson" / a pasted `devcoach:lesson:` code, link or URL →
|
|
@@ -15,7 +15,7 @@ Group the 10 lessons by `topic_id`. For each topic that appeared:
|
|
|
15
15
|
|
|
16
16
|
| Signal | Condition | Action |
|
|
17
17
|
|---|---|---|
|
|
18
|
-
| Consistent mastery | All feedback `know`, no `dont_know` | `update_knowledge(topic, +1)` if confidence < 9 |
|
|
18
|
+
| Consistent mastery | All feedback `know` or `understood`, no `dont_know` | `update_knowledge(topic, +1)` if confidence < 9 |
|
|
19
19
|
| Persistent gap | 2+ lessons on same topic, any `dont_know` | `update_knowledge(topic, -1)` if confidence > 1 |
|
|
20
20
|
| Recurring topic | 3+ lessons on same topic, mixed or no feedback | no confidence change — note in notebook |
|
|
21
21
|
| New topic | `topic_id` absent from `get_profile` | `add_topic` — see Step 3 |
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Courses — a step-by-step path from what the user knows to what they couldn't follow
|
|
2
|
+
|
|
3
|
+
A course is the answer to ❌ *couldn't follow*: instead of another lesson at the same
|
|
4
|
+
height, a path that starts where the user actually stands. It is **user-initiated**
|
|
5
|
+
("explain this properly", "teach me X step by step", "start a course", `/devcoach:course`,
|
|
6
|
+
or a *yes* to the one offer you make after a ❌) and it runs **in chat**; the dashboard is
|
|
7
|
+
the viewer (Courses page, the document, the progress). The course itself is ONE rich,
|
|
8
|
+
self-contained HTML document that **you write with your file tools** at the path
|
|
9
|
+
`create_course` returns — never pasted into the chat.
|
|
10
|
+
|
|
11
|
+
Never start a course inside a cued turn: resolve the cue first (`log_lesson` or
|
|
12
|
+
`skip_lesson`, the hard rules), then begin on the user's next message. While a course is
|
|
13
|
+
active the hooks pause lesson cues, so the conversation is yours until it is completed or
|
|
14
|
+
abandoned — finish it, or set it `abandoned`, rather than leaving it hanging.
|
|
15
|
+
|
|
16
|
+
**Ask with choices when the answer is a pick; ask in text when the answer must be typed.**
|
|
17
|
+
In a client with a question UI (AskUserQuestion in Claude Code) every yes / no / roughly
|
|
18
|
+
question, every "which of these" and every offer is presented as selectable options — the
|
|
19
|
+
user should never have to type *yes*. A question whose answer is a snippet's output, a value
|
|
20
|
+
or an explanation stays free text. Without a question UI (Gemini CLI, Codex CLI) use the
|
|
21
|
+
text form with the options in parentheses.
|
|
22
|
+
|
|
23
|
+
## 1. Seed
|
|
24
|
+
|
|
25
|
+
- From a lesson: the one the user points at — by id, or **by description**: run
|
|
26
|
+
`get_lessons({search, limit: 5})` with the 2–3 most specific words of what they said
|
|
27
|
+
(`search` covers title, topic, summary and body). One match → it is the seed; say so in
|
|
28
|
+
one line (*"Seed: **HTMX and redirects** · Sep 19"*). Several → offer them as choices
|
|
29
|
+
(title · date · feedback icon) plus a last *"None of these — a free concept"*. None → a
|
|
30
|
+
free concept. No argument at all → the newest `get_lessons({feedback: "dont_know", limit: 1})`,
|
|
31
|
+
else the newest lesson.
|
|
32
|
+
- **A course may already exist for that seed**: check `get_courses({lesson_id})` (or
|
|
33
|
+
`get_courses({status: "active"})` for a free concept with the same title). If one does,
|
|
34
|
+
ask before anything else, as choices: **Continue it** (only when active — resume at its
|
|
35
|
+
first `todo` step), **Redo it** (set it `abandoned`, then create a fresh one), **Keep both**
|
|
36
|
+
(a second course, the id gets a `-2` suffix), **Cancel**. Never overwrite a course
|
|
37
|
+
document without this question.
|
|
38
|
+
- Keep the lesson's `topic_id`; its `task_context` and git metadata come back from
|
|
39
|
+
`create_course` as `seed_context` — draw examples from that work.
|
|
40
|
+
- From a free concept ("teach me logarithms"): pick a `topic_id` as for a lesson.
|
|
41
|
+
- Read `get_briefing` once: the profile's confidence on the topic sets the depth of every
|
|
42
|
+
step, and a tracked topic at confidence ≥ 6 is a floor the exploration never digs below.
|
|
43
|
+
|
|
44
|
+
## 2. Explore the prerequisites — one question per message
|
|
45
|
+
|
|
46
|
+
Build the chain **top-down**, from the target concept toward the ground the user stands
|
|
47
|
+
on, asking ONE question per message and waiting for the answer:
|
|
48
|
+
|
|
49
|
+
> To explain **logarithms** I first need **powers**. Do you know what 2³ means?
|
|
50
|
+
|
|
51
|
+
Offer the three answers as choices — **Yes** ("I can explain it") · **Roughly** ("seen it,
|
|
52
|
+
a refresher would help") · **No** ("new to me") — or, without a question UI, end the line
|
|
53
|
+
with *(yes / no / roughly)*.
|
|
54
|
+
|
|
55
|
+
- *yes* → stop: that concept is the floor. *roughly* → treat as known but plan a short
|
|
56
|
+
refresher as step 1. *no* → go one level down ("to explain powers I need multiplication…").
|
|
57
|
+
- Stop at the first *yes*, at a profile topic with confidence ≥ 6, or at depth 5 — a
|
|
58
|
+
chain longer than that is two courses; say so and start with the lower one.
|
|
59
|
+
- Keep it to one question per message, no lecturing between questions, no "great!".
|
|
60
|
+
- Record the chain as `[{concept, known}]` top-down, then reflect it back in one line:
|
|
61
|
+
*"So: sums ✓ → multiplication ✗ → powers ✗ → logarithms ✗. I'll start from multiplication."*
|
|
62
|
+
|
|
63
|
+
## 3. Plan and create
|
|
64
|
+
|
|
65
|
+
- 3–7 steps, **bottom-up** from the first known concept to the target; each step = one idea
|
|
66
|
+
that fits in working memory — a step the reader finishes in a few minutes beats a
|
|
67
|
+
complete one. Kinds: `concept` (the idea), `example` (worked, from the user's own work
|
|
68
|
+
when `seed_context` allows), `practice` (try it), `check` (a question with a reveal).
|
|
69
|
+
- `create_course({title, topic_id, goal, lesson_id?, prerequisites})`. Its result carries
|
|
70
|
+
`course_dir`, `document_path` and `seed_context`.
|
|
71
|
+
|
|
72
|
+
## 4. Write the document
|
|
73
|
+
|
|
74
|
+
ONE file at `document_path`, then `add_course_step` per section in order
|
|
75
|
+
(`anchor` = the section id). The viewer serves the file inside a sandboxed frame with a
|
|
76
|
+
strict CSP, and the same file must read well opened on its own — so the contract:
|
|
77
|
+
|
|
78
|
+
**Self-contained.** Inline `<style>` and inline `<script>` only. **No external URLs** (no CDN,
|
|
79
|
+
fonts, images from the web), **no `fetch`/XHR**, **no `<form>`** (buttons with click handlers
|
|
80
|
+
instead), **no `alert`/`confirm`/`prompt`**, no `target="_blank"` links out. Anything outside
|
|
81
|
+
this fails silently in the viewer.
|
|
82
|
+
|
|
83
|
+
**One step at a time.** A left **step menu** (`<nav class="steps">`: numbered entries, the
|
|
84
|
+
current one highlighted) and a content pane that shows **exactly one `<section id="step-N">`**;
|
|
85
|
+
under 900 px the menu becomes a strip above the content. Routing by `location.hash`
|
|
86
|
+
(`#step-N`, no hash → step 1): on `hashchange` show that section, scroll to the top. Every
|
|
87
|
+
section ends with *← Previous · Next →* buttons and the line *"Stuck? Ask in the chat — that's
|
|
88
|
+
where the course is being taught."* Mark `:root[data-embedded] nav.steps { display: none }`:
|
|
89
|
+
the dashboard injects `data-embedded`, has its own step list, makes the canvas transparent and
|
|
90
|
+
lifts the `max-width` of `.course`, `section` and `header.intro` to use the width it has — so
|
|
91
|
+
keep those class names.
|
|
92
|
+
|
|
93
|
+
**Each section**, in order: **Goal** (one line) → **The idea** (short, one concept, an analogy
|
|
94
|
+
where it helps) → **Worked example** → **Try it** (see the ladder below) → **Check yourself**
|
|
95
|
+
(one question, a *Reveal* button; the reveal says what each wrong answer usually means). Depth
|
|
96
|
+
from the user's confidence band on the topic; vocabulary introduced before use. Quiz options
|
|
97
|
+
have the same length and no formatting hint — the wording carries no clue.
|
|
98
|
+
|
|
99
|
+
**Try it — a ladder by language.** The code is in the **seed's language** (the lesson's
|
|
100
|
+
`categories` / `seed_context`: Java, C#, Rust, Go, Python, Swift, Kotlin, SQL…) and is never
|
|
101
|
+
translated into JS just to make it runnable:
|
|
102
|
+
|
|
103
|
+
1. JS, TS-as-JS, HTML, CSS, regex, SQL-in-JS → an **editable box that really runs**:
|
|
104
|
+
`new Function(code)` inside `try/catch` (the viewer's CSP allows eval), errors rendered
|
|
105
|
+
into the page, output captured through a local `log()` — never `console` alone.
|
|
106
|
+
2. Any other language → the snippet stays read-only and highlighted in that language, and
|
|
107
|
+
the interaction is one of: a **JS simulation of the concept** (toggles and buttons driving
|
|
108
|
+
a model of the semantics — labelled *simulation*, never a fake "Run"), **predict the
|
|
109
|
+
output** (typed or picked, then Reveal), **fill the blank**, or **spot the bug** (3–4
|
|
110
|
+
candidates). No button may pretend to execute Java, Rust or Go.
|
|
111
|
+
|
|
112
|
+
**Design.** One measure (~68ch), generous spacing, quiet Tufte-like typography; light + dark
|
|
113
|
+
using the dashboard's palette (greys, indigo `#4f46e5` accents, rose for warnings). It must
|
|
114
|
+
also pass as a Claude artifact page: `<title>` is a 2–4 word name (the long title is the
|
|
115
|
+
`<h1>`), colour tokens live on `:root`, dark tokens are set under
|
|
116
|
+
`@media (prefers-color-scheme: dark)` guarded by `:root:not([data-theme="light"])` and again
|
|
117
|
+
under `:root[data-theme="dark"]`, `body` has an explicit background, 16 px side gutters at
|
|
118
|
+
phone width, no horizontal page scroll.
|
|
119
|
+
|
|
120
|
+
Skeleton (adapt, keep the ids and the class names):
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
124
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
125
|
+
<title>Node error events</title>
|
|
126
|
+
<style>
|
|
127
|
+
:root { --bg:#f8fafc; --panel:#fff; --text:#0f172a; --muted:#64748b; --line:#e2e8f0; --accent:#4f46e5; --accent-soft:#eef2ff; --warn:#e11d48; }
|
|
128
|
+
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg:#0b1220; --panel:#111a2e; --text:#e2e8f0; --muted:#94a3b8; --line:#1e293b; --accent:#818cf8; --accent-soft:#1e1b4b; --warn:#fb7185; } }
|
|
129
|
+
:root[data-theme="dark"] { --bg:#0b1220; --panel:#111a2e; --text:#e2e8f0; --muted:#94a3b8; --line:#1e293b; --accent:#818cf8; --accent-soft:#1e1b4b; --warn:#fb7185; }
|
|
130
|
+
body { margin:0; background:var(--bg); color:var(--text); font:16px/1.6 system-ui, sans-serif; }
|
|
131
|
+
.course { display:grid; grid-template-columns:16rem minmax(0,1fr); gap:24px; max-width:1100px; margin:0 auto; padding:24px 16px; }
|
|
132
|
+
nav.steps { position:sticky; top:16px; align-self:start; } nav.steps a { display:block; padding:6px 10px; border-radius:8px; color:var(--muted); text-decoration:none; }
|
|
133
|
+
nav.steps a.current { background:var(--accent-soft); color:var(--accent); }
|
|
134
|
+
:root[data-embedded] nav.steps { display:none } :root[data-embedded] .course { grid-template-columns:minmax(0,1fr); }
|
|
135
|
+
section { display:none; max-width:68ch; } section.current { display:block; }
|
|
136
|
+
@media (max-width: 900px) { .course { grid-template-columns:minmax(0,1fr); } nav.steps { position:static; display:flex; flex-wrap:wrap; gap:4px; } }
|
|
137
|
+
/* .goal, .try, .check, .pager, buttons … */
|
|
138
|
+
</style></head><body>
|
|
139
|
+
<div class="course">
|
|
140
|
+
<nav class="steps"><a href="#step-1">1 · Sums</a><a href="#step-2">2 · Powers</a></nav>
|
|
141
|
+
<main>
|
|
142
|
+
<section id="step-1"><h2>1 · Sums</h2><p class="goal">…</p> … <div class="check">…<button>Reveal</button></div>
|
|
143
|
+
<div class="pager"><a href="#step-1">← Previous</a><a href="#step-2">Next →</a></div>
|
|
144
|
+
<p class="ask">Stuck? Ask in the chat — that's where the course is being taught.</p></section>
|
|
145
|
+
<section id="step-2">…</section>
|
|
146
|
+
</main>
|
|
147
|
+
</div>
|
|
148
|
+
<script>
|
|
149
|
+
// show one section: on load + hashchange, toggle .current on the section and its nav link, scrollTo(0,0)
|
|
150
|
+
// reveal buttons; the live examples (new Function inside try/catch) or the simulations
|
|
151
|
+
</script></body></html>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4b. Share as an artifact — an offer, nothing more
|
|
155
|
+
|
|
156
|
+
After the last `add_course_step`, **if the Artifact tool is available**, offer ONCE, as a
|
|
157
|
+
choice: *"Publish this course as a Claude artifact too? (private until you share the
|
|
158
|
+
link)"* — **Yes** → publish `document_path` as it is (`icon: "course"`, `description` = the
|
|
159
|
+
goal) and reply with the link in one line; **No**, or no Artifact tool → nothing. The local
|
|
160
|
+
file stays the course; the artifact is a copy for sharing, and the user can ask for it again
|
|
161
|
+
any time ("publish the course as an artifact"). Never store the link anywhere.
|
|
162
|
+
|
|
163
|
+
## 5. Teach — one step per message
|
|
164
|
+
|
|
165
|
+
For each step, in chat: 4–8 lines of the idea in your own words, the pointer *"Step N is on
|
|
166
|
+
the dashboard → Courses"*, then the step's check question. Then wait.
|
|
167
|
+
|
|
168
|
+
Classify the check before asking: a **pick** (the order of N lines, yes/no, which of 3–4
|
|
169
|
+
candidates, spot the bug) is offered as choices with a last *"Not sure"*; a **typed** answer
|
|
170
|
+
(write the output, name the value, explain why) is asked as text. The re-check after a miss
|
|
171
|
+
follows the same rule.
|
|
172
|
+
|
|
173
|
+
- Correct → `update_course_progress({course_id, position, status: "done"})` and the next
|
|
174
|
+
step in the next message.
|
|
175
|
+
- Wrong or unsure → re-explain from a different angle (a new example, a smaller piece);
|
|
176
|
+
never advance silently. Two misses → offer to split the step: add the new `<section>` to
|
|
177
|
+
the document right after the current one (update the nav and the pagers) and register it
|
|
178
|
+
with `add_course_step({…, after: <current position>})` so the order matches.
|
|
179
|
+
- "skip" → `status: "skipped"` and move on. "stop" / "later" → leave it `active`; it
|
|
180
|
+
resumes with `get_courses({status: "active"})` ("continue the course").
|
|
181
|
+
|
|
182
|
+
## 6. Finish
|
|
183
|
+
|
|
184
|
+
When every step is done or skipped the course completes itself. Close with two lines: what
|
|
185
|
+
the user can now do, and where the document lives. If the course grew from a ❌ lesson,
|
|
186
|
+
offer ONCE, as a **Yes / No** choice: *"Want me to mark that lesson as understood now?"* →
|
|
187
|
+
`submit_feedback(lesson_id, "understood")` only on a yes. Offer nothing else — no star, no
|
|
188
|
+
share, no new lesson.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Injected by the dashboard at the end of every course document it serves (never part of the
|
|
2
|
+
// file on disk). Inside the sandboxed frame the parent cannot measure the document, so the
|
|
3
|
+
// document reports its own height and the step it is showing; the parent grows the frame to fit
|
|
4
|
+
// (the page scrolls, never the frame) and moves its step highlight. Standalone opens (a pasted
|
|
5
|
+
// URL, the artifact copy) never run this: `parent === window` exits at once.
|
|
6
|
+
(function () {
|
|
7
|
+
if (window.parent === window) return;
|
|
8
|
+
var root = document.documentElement;
|
|
9
|
+
root.setAttribute("data-embedded", "1");
|
|
10
|
+
// Embedded, the page canvas is the dashboard's: let it show through (sections keep their own
|
|
11
|
+
// panel colours), so the frame reads as part of the page instead of a box inside it.
|
|
12
|
+
root.style.background = "transparent";
|
|
13
|
+
// …and use the width the dashboard gives it: the reading-column caps of the contract's
|
|
14
|
+
// `.course` / `section` / `header.intro` are for the standalone page.
|
|
15
|
+
var style = document.createElement("style");
|
|
16
|
+
style.textContent =
|
|
17
|
+
":root[data-embedded] .course{max-width:none;padding:4px 0 32px}" +
|
|
18
|
+
":root[data-embedded] section,:root[data-embedded] header.intro{max-width:none}";
|
|
19
|
+
(document.head || root).appendChild(style);
|
|
20
|
+
function clearBody() { document.body.style.background = "transparent"; }
|
|
21
|
+
if (document.body) clearBody(); else document.addEventListener("DOMContentLoaded", clearBody);
|
|
22
|
+
function report() {
|
|
23
|
+
var id = location.hash.replace(/^#/, "");
|
|
24
|
+
var target = id ? document.getElementById(id) : null;
|
|
25
|
+
window.parent.postMessage(
|
|
26
|
+
{ type: "devcoach:course", height: root.scrollHeight, anchor: target ? id : null },
|
|
27
|
+
"*",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
window.addEventListener("load", report);
|
|
31
|
+
window.addEventListener("hashchange", report);
|
|
32
|
+
if (typeof ResizeObserver === "function") new ResizeObserver(report).observe(root);
|
|
33
|
+
else setInterval(report, 500);
|
|
34
|
+
report();
|
|
35
|
+
})();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Course page: the sandboxed frame reports its height and current step (course-frame.js, injected
|
|
2
|
+
// by the server); this side sizes the frame to the document so the page scrolls, keeps the step
|
|
3
|
+
// list in sync, and turns step clicks into in-frame navigation (a fragment navigation of the
|
|
4
|
+
// cross-origin frame) instead of a page reload.
|
|
5
|
+
(function () {
|
|
6
|
+
var frame = document.getElementById("course-frame");
|
|
7
|
+
var list = document.getElementById("course-steps");
|
|
8
|
+
if (!frame || !list) return;
|
|
9
|
+
var CURRENT = ["bg-indigo-50", "dark:bg-indigo-900/30"];
|
|
10
|
+
var shown = null;
|
|
11
|
+
|
|
12
|
+
function select(anchor) {
|
|
13
|
+
if (!anchor || anchor === shown) return;
|
|
14
|
+
shown = anchor;
|
|
15
|
+
list.querySelectorAll("li[data-anchor]").forEach(function (li) {
|
|
16
|
+
var on = li.dataset.anchor === anchor;
|
|
17
|
+
CURRENT.forEach(function (cls) {
|
|
18
|
+
li.classList.toggle(cls, on);
|
|
19
|
+
});
|
|
20
|
+
var actions = li.querySelector("[data-step-actions]");
|
|
21
|
+
if (actions) actions.classList.toggle("hidden", !on);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function scrollToFrame() {
|
|
26
|
+
var top = frame.getBoundingClientRect().top + window.scrollY - 16;
|
|
27
|
+
window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
window.addEventListener("message", function (event) {
|
|
31
|
+
var data = event.data;
|
|
32
|
+
if (event.source !== frame.contentWindow || !data || data.type !== "devcoach:course") return;
|
|
33
|
+
var height = Number(data.height);
|
|
34
|
+
if (Number.isFinite(height)) {
|
|
35
|
+
frame.style.height = Math.min(50000, Math.max(320, Math.ceil(height))) + "px";
|
|
36
|
+
}
|
|
37
|
+
if (typeof data.anchor === "string") select(data.anchor);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
list.addEventListener("click", function (event) {
|
|
41
|
+
var link = event.target.closest("a[data-anchor]");
|
|
42
|
+
if (!link) return;
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
var anchor = link.dataset.anchor;
|
|
45
|
+
try {
|
|
46
|
+
// Only `href` is settable on a cross-origin Location; same URL + new fragment is a
|
|
47
|
+
// fragment navigation inside the frame (hashchange, no reload).
|
|
48
|
+
frame.contentWindow.location.href = frame.src.split("#")[0] + "#" + anchor;
|
|
49
|
+
} catch {
|
|
50
|
+
window.location.href = link.href;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
select(anchor);
|
|
54
|
+
scrollToFrame();
|
|
55
|
+
});
|
|
56
|
+
})();
|