Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
519a04a
Port the Nexus decouple-monolith code to TypeScript
nadvolod Aug 28, 2026
6bd659b
Port the Instruqt track to TypeScript
nadvolod Aug 28, 2026
84fc1e1
Build the TypeScript sandbox image and pin it by digest
nadvolod Aug 28, 2026
930fa46
Publish the TypeScript track in maintenance mode
nadvolod Aug 28, 2026
b511d0b
Publish the TypeScript track and record its assigned ids
nadvolod Aug 28, 2026
6a882e4
Document the TypeScript track in the README
nadvolod Aug 29, 2026
3af680d
Make the workshop standalone: no references to other language versions
nadvolod Aug 30, 2026
ee3da0b
Take the TypeScript track out of maintenance mode
nadvolod Aug 30, 2026
d5c9c22
Give checkCompliance as a worked example in challenge 3
nadvolod Aug 30, 2026
54d39c8
Rebuild and re-pin the sandbox image for the worked example
nadvolod Aug 30, 2026
b5aa20c
Fix autosave and improve IntelliSense in the sandbox editor
nadvolod Aug 30, 2026
e210ca6
Sync track checksum after publishing live
nadvolod Aug 30, 2026
6d3836d
Explain the TS7006 a learner hits when the contract is empty
nadvolod Aug 30, 2026
685b2b6
Explain the Go system Worker attendees see in the Workers list
nadvolod Aug 30, 2026
53f92fa
Sync track checksum after publishing the assignment fixes
nadvolod Aug 30, 2026
8cc66ae
Merge the Exercise and Solution tabs into one code tab
nadvolod Aug 30, 2026
046db2d
Merge TODOs 4 and 5, and cut the TODO comments back
nadvolod Aug 30, 2026
765600c
Sync track checksum after publishing the merged TODO 4
nadvolod Aug 30, 2026
6bb03c1
Split TODO 4 into 4a/4b/4c and keep recipes out of the assignments
nadvolod Aug 31, 2026
b283315
Correct the release stage: Nexus is GA in the TypeScript SDK
nadvolod Aug 31, 2026
3909d36
Turn on the Instruqt feedback tab and ask for feedback in every chall…
nadvolod Aug 31, 2026
548dd32
Sync track checksum after enabling feedback
nadvolod Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/build-and-push-typescript.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Pushes the TypeScript Instruqt track definition on merge to main.
#
# CI deliberately does NOT build the sandbox image, for the same reason as the
# Kotlin caller: the image lives at ghcr.io/nadvolod/edu-nexus-typescript-sandbox,
# a personal namespace this repo's default GITHUB_TOKEN cannot write to. A build
# job would fail on every merge. Rather than leave CI permanently red, the image
# is built by hand and pinned by digest in typescript/instruqt/config.yml:
#
# cd typescript
# docker buildx build --platform linux/amd64 -f sandbox/Dockerfile \
# -t ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest --push .
# docker buildx imagetools inspect ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest \
# --format "{{.Manifest.Digest}}"
# # paste that digest into typescript/instruqt/config.yml, then commit
#
# Because the digest is pinned, a source change only reaches attendees once that
# re-pin is committed, and the re-pin lives under typescript/instruqt/. That is
# why this workflow watches instruqt/ alone: it is the only path that can change
# what the track actually serves.
#
# To let CI own the image later, move it to ghcr.io/temporalio/... (or add a PAT
# with write:packages) and restore build_image below.
#
# Required once: a repo secret named INSTRUQT_TOKEN (Instruqt org API key).
name: Publish TypeScript Nexus track

on:
push:
branches: [main]
paths:
- "typescript/instruqt/**"
- ".github/workflows/_track-ci.yml"
- ".github/workflows/build-and-push-typescript.yml"
workflow_dispatch:

jobs:
ci:
# packages: write looks wrong next to build_image: false, but it is required.
# A caller's permissions block is the CEILING for every job in the called
# workflow, and GitHub validates that ceiling when it loads the workflow,
# before any `if:` is evaluated. _track-ci.yml's build-image job asks for
# packages: write, so a read-only ceiling fails the whole run at startup with
# zero jobs, even though build-image never runs. Do not narrow this back.
permissions:
contents: read
packages: write
uses: ./.github/workflows/_track-ci.yml
with:
track_dir: "typescript"
# Referenced for completeness. Unused while build_image is false.
image: ghcr.io/nadvolod/edu-nexus-typescript-sandbox
# Never build here: see the note at the top of this file.
build_image: false
push_track: true
secrets: inherit
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ build/

# VS Code Kotlin language server output
bin/

# Node (typescript/)
node_modules/
lib/
109 changes: 109 additions & 0 deletions INSTRUQT.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,61 @@ fixed code-server for us. Confirmed working.
Only one tab can be first, so a second embedded app (our Solution editor) may
still need a refresh. Accept it, or serve both views from one instance.

### Use ONE editor tab, not an Exercise tab and a Solution tab

This is the fix for the 0x0 iframe problem above rather than a workaround for it.

Two editor tabs means two code-server iframes, and Instruqt boots every service tab's
iframe at challenge start including the hidden ones. Only the first tab has real
dimensions, so the second reliably needs a manual refresh. Making the Solution tab
position 5 does not help; it just moves which one is broken.

Point a single tab at the directory that contains BOTH trees:

```yaml
- title: Exercise
type: service
path: /?folder=/root/workshop # holds exercise/ and solution/
port: 8080
```

One iframe, nothing hidden, no refresh. It also drops the tab count from seven to six.

Three things move with it, and two fail silently:

- **`tab-N` is a position.** Removing a tab renumbers every tab after it. Re-verify
every `[button label="..."](tab-N)` by label, not by arithmetic.
- **File paths in assignments become ambiguous.** With `solution/` visible, "open
`payments/workflows.ts`" matches two files, and a learner editing the solution copy
watches their changes do nothing. Write paths in full:
`exercise/src/payments/workflows.ts`.
- **Any readiness probe that polls `?folder=` must use the new path.** Ours still
pointed at the old folder, so setup would have reported the editor not ready forever.

The cost is that source sits two levels deeper in the tree. Worth it; the refresh bug
was hit by every attendee, the extra clicks are hit once.

### Feedback is lab chrome, not a tab you add

Do not add a Feedback entry to every challenge's `tabs:` list. It renumbers every tab
after it and breaks every `[button](tab-N)` in the track, for a tab Instruqt already
provides.

Turn it on in `track.yml` instead:

```yaml
lab_config:
feedback_tab_enabled: true # Feedback tab in the lab UI
feedback_recap_enabled: true # prompt at the end of the track
```

Verified against `temporal-community/ai-agents-workshop-v4`, which sets both and has no
Feedback entry in any challenge's `tabs:`. Because it is chrome, `tab-N` indices are
untouched.

There is no `tab-N` to link to, so a per-challenge call to action references it by name:
"the **Feedback** tab".

### Reordering tabs breaks every button, silently

`tab-N` is a **zero-indexed position, not an id**. Move a tab and every
Expand Down Expand Up @@ -324,6 +379,60 @@ grep -rn '^ *==*$' */assignment.md
lines in a `kotlin,nocopy` fence and then say what they mean. Costs four lines of
assignment, removes all the guessing.

### Implementation steps belong in the TODO, not the assignment

The assignment names the file and the TODO, and says why the change matters. The TODO
comment, three lines from the cursor, says how. When both say how you have two copies to
keep in sync, and the learner reads the same recipe twice.

This does not contradict "Show the line, do not describe it" above, and the difference is
worth stating precisely:

- Code the learner is meant to **read** belongs in the assignment. Pasting it stops them
hunting through a file for the two lines you meant.
- Code the learner is meant to **write** belongs in the TODO. That is the recipe, and the
recipe goes next to the cursor.

What it looks like when it leaks, from challenge 4 of the TypeScript track:

```
Then delete the proxy above it and its `complianceActivities` import.
Remove `...complianceActivities` from the `activities` object, and delete its import.
```

Both were already in the TODO comment. Grep for the shape — an imperative aimed at a
named symbol:

```bash
grep -nE '^(Remove|Delete|Add|Replace|Change) `' */assignment.md
```

The assignment keeps the part the TODO cannot carry: why the change matters, and what to
notice once it works.

### One action, one lettered TODO, sited on the code it changes

A TODO that says "replace this call, then delete the declaration above, then write the
function at the bottom" makes the learner hold three locations in their head and scroll
between them. They finish one, lose the list, and re-read the whole block.

Split by action, letter them so they still read as one idea, and put each comment on the
code it acts on:

```
TODO 4a on the proxy to delete
TODO 4b on the call site to replace
TODO 4c on the function to write
```

Letters rather than new numbers when the parts are one concept — here, putting the Nexus
boundary in. New numbers imply separate concepts and inflate the count.

The failure this fixes is real and was reported from a live sandbox: a note reading
"TODO 4(a) deletes this proxy" sat on the proxy while the instructions for 4(a) were
thirty lines away at the call site, so the two halves of one instruction pointed at each
other.

### A long edge label will cover your diagram

Node text and edge labels are drawn in different layers, so a label wider than the gap
Expand Down
Loading